@mcoda/core 0.1.8 → 0.1.9
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/CHANGELOG.md +3 -0
- package/dist/api/AgentsApi.d.ts +8 -1
- package/dist/api/AgentsApi.d.ts.map +1 -1
- package/dist/api/AgentsApi.js +70 -0
- package/dist/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +2 -0
- package/dist/api/TasksApi.d.ts.map +1 -1
- package/dist/api/TasksApi.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +3 -1
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +2 -0
- package/dist/services/agents/AgentRatingFormula.d.ts +27 -0
- package/dist/services/agents/AgentRatingFormula.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingFormula.js +45 -0
- package/dist/services/agents/AgentRatingService.d.ts +41 -0
- package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingService.js +299 -0
- package/dist/services/agents/GatewayAgentService.d.ts +3 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
- package/dist/services/agents/GatewayAgentService.js +68 -24
- package/dist/services/agents/GatewayHandoff.d.ts +7 -0
- package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
- package/dist/services/agents/GatewayHandoff.js +108 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +1 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +19 -16
- package/dist/services/docs/DocsService.d.ts +11 -1
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +240 -52
- package/dist/services/execution/GatewayTrioService.d.ts +133 -0
- package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
- package/dist/services/execution/GatewayTrioService.js +1125 -0
- package/dist/services/execution/QaFollowupService.d.ts +1 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
- package/dist/services/execution/QaFollowupService.js +1 -0
- package/dist/services/execution/QaProfileService.d.ts +6 -0
- package/dist/services/execution/QaProfileService.d.ts.map +1 -1
- package/dist/services/execution/QaProfileService.js +165 -3
- package/dist/services/execution/QaTasksService.d.ts +18 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +712 -34
- package/dist/services/execution/WorkOnTasksService.d.ts +14 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +1497 -240
- package/dist/services/openapi/OpenApiService.d.ts +10 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +66 -10
- package/dist/services/planning/CreateTasksService.d.ts +6 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +261 -28
- package/dist/services/planning/RefineTasksService.d.ts +5 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +184 -35
- package/dist/services/review/CodeReviewService.d.ts +14 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +657 -61
- package/dist/services/shared/ProjectGuidance.d.ts +6 -0
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
- package/dist/services/shared/ProjectGuidance.js +21 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts +20 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts.map +1 -0
- package/dist/services/tasks/TaskCommentFormatter.js +54 -0
- package/dist/workspace/WorkspaceManager.d.ts +4 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
- package/dist/workspace/WorkspaceManager.js +3 -0
- package/package.json +5 -5
|
@@ -6,17 +6,28 @@ import { setTimeout as delay } from "node:timers/promises";
|
|
|
6
6
|
import { DocdexClient } from "@mcoda/integrations";
|
|
7
7
|
import { JobService } from "../jobs/JobService.js";
|
|
8
8
|
import { RoutingService } from "../agents/RoutingService.js";
|
|
9
|
+
import { AgentRatingService } from "../agents/AgentRatingService.js";
|
|
9
10
|
import { createEpicKeyGenerator, createStoryKeyGenerator, createTaskKeyGenerator, } from "./KeyHelpers.js";
|
|
10
11
|
const formatBullets = (items, fallback) => {
|
|
11
12
|
if (!items || items.length === 0)
|
|
12
13
|
return `- ${fallback}`;
|
|
13
14
|
return items.map((item) => `- ${item}`).join("\n");
|
|
14
15
|
};
|
|
16
|
+
const formatTestList = (items) => {
|
|
17
|
+
if (!items || items.length === 0)
|
|
18
|
+
return "Not applicable";
|
|
19
|
+
return items.join("; ");
|
|
20
|
+
};
|
|
15
21
|
const ensureNonEmpty = (value, fallback) => value && value.trim().length > 0 ? value.trim() : fallback;
|
|
16
22
|
const estimateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
17
23
|
const DOC_CONTEXT_BUDGET = 8000;
|
|
24
|
+
const DOCDEX_HANDLE = /^docdex:/i;
|
|
25
|
+
const VALID_AREAS = new Set(["web", "adm", "bck", "ops", "infra", "mobile"]);
|
|
26
|
+
const VALID_TASK_TYPES = new Set(["feature", "bug", "chore", "spike"]);
|
|
18
27
|
const inferDocType = (filePath) => {
|
|
19
28
|
const name = path.basename(filePath).toLowerCase();
|
|
29
|
+
if (name.includes("openapi") || name.includes("swagger"))
|
|
30
|
+
return "OPENAPI";
|
|
20
31
|
if (name.includes("sds"))
|
|
21
32
|
return "SDS";
|
|
22
33
|
if (name.includes("pdr"))
|
|
@@ -25,6 +36,48 @@ const inferDocType = (filePath) => {
|
|
|
25
36
|
return "RFP";
|
|
26
37
|
return "DOC";
|
|
27
38
|
};
|
|
39
|
+
const normalizeArea = (value) => {
|
|
40
|
+
if (typeof value !== "string")
|
|
41
|
+
return undefined;
|
|
42
|
+
const tokens = value
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.split(/[^a-z]+/)
|
|
45
|
+
.map((token) => token.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
for (const token of tokens) {
|
|
48
|
+
if (VALID_AREAS.has(token))
|
|
49
|
+
return token;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
const normalizeTaskType = (value) => {
|
|
54
|
+
if (typeof value !== "string")
|
|
55
|
+
return undefined;
|
|
56
|
+
const tokens = value
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.split(/[^a-z]+/)
|
|
59
|
+
.map((token) => token.trim())
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
for (const token of tokens) {
|
|
62
|
+
if (VALID_TASK_TYPES.has(token))
|
|
63
|
+
return token;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
};
|
|
67
|
+
const normalizeRelatedDocs = (value) => {
|
|
68
|
+
if (!Array.isArray(value))
|
|
69
|
+
return [];
|
|
70
|
+
return value
|
|
71
|
+
.map((entry) => {
|
|
72
|
+
if (typeof entry === "string")
|
|
73
|
+
return entry;
|
|
74
|
+
if (entry && typeof entry === "object" && "handle" in entry && typeof entry.handle === "string") {
|
|
75
|
+
return entry.handle;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
})
|
|
79
|
+
.filter((entry) => Boolean(entry && DOCDEX_HANDLE.test(entry)));
|
|
80
|
+
};
|
|
28
81
|
const describeDoc = (doc, idx) => {
|
|
29
82
|
const title = doc.title ?? doc.path ?? doc.id ?? `doc-${idx + 1}`;
|
|
30
83
|
const source = doc.path ?? doc.id ?? "docdex";
|
|
@@ -32,19 +85,80 @@ const describeDoc = (doc, idx) => {
|
|
|
32
85
|
return `- [${doc.docType}] ${title} (handle: docdex:${doc.id ?? `doc-${idx + 1}`}, source: ${source})${head ? `\n Excerpt: ${head}` : ""}`;
|
|
33
86
|
};
|
|
34
87
|
const extractJson = (raw) => {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
88
|
+
const fencedMatches = [...raw.matchAll(/```json([\s\S]*?)```/g)].map((match) => match[1]);
|
|
89
|
+
const stripped = raw.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
90
|
+
const candidates = [...fencedMatches, stripped, raw].filter((candidate) => candidate.trim().length > 0);
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
const parsed = tryParseJson(candidate);
|
|
93
|
+
if (parsed && isPlanShape(parsed))
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
};
|
|
98
|
+
const isPlanShape = (value) => {
|
|
99
|
+
if (!value || typeof value !== "object")
|
|
100
|
+
return false;
|
|
101
|
+
return Array.isArray(value.epics) || Array.isArray(value.stories) || Array.isArray(value.tasks);
|
|
102
|
+
};
|
|
103
|
+
const tryParseJson = (value) => {
|
|
42
104
|
try {
|
|
43
|
-
return JSON.parse(
|
|
105
|
+
return JSON.parse(value);
|
|
44
106
|
}
|
|
45
107
|
catch {
|
|
46
|
-
|
|
108
|
+
// continue
|
|
109
|
+
}
|
|
110
|
+
const objects = extractJsonObjects(value).reverse();
|
|
111
|
+
for (const obj of objects) {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(obj);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// continue
|
|
117
|
+
}
|
|
47
118
|
}
|
|
119
|
+
return undefined;
|
|
120
|
+
};
|
|
121
|
+
const extractJsonObjects = (value) => {
|
|
122
|
+
const results = [];
|
|
123
|
+
let depth = 0;
|
|
124
|
+
let start = -1;
|
|
125
|
+
let inString = false;
|
|
126
|
+
let escaped = false;
|
|
127
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
128
|
+
const ch = value[i];
|
|
129
|
+
if (inString) {
|
|
130
|
+
if (escaped) {
|
|
131
|
+
escaped = false;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ch === "\\") {
|
|
135
|
+
escaped = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === "\"")
|
|
139
|
+
inString = false;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (ch === "\"") {
|
|
143
|
+
inString = true;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (ch === "{") {
|
|
147
|
+
if (depth === 0)
|
|
148
|
+
start = i;
|
|
149
|
+
depth += 1;
|
|
150
|
+
}
|
|
151
|
+
else if (ch === "}") {
|
|
152
|
+
if (depth === 0)
|
|
153
|
+
continue;
|
|
154
|
+
depth -= 1;
|
|
155
|
+
if (depth === 0 && start >= 0) {
|
|
156
|
+
results.push(value.slice(start, i + 1));
|
|
157
|
+
start = -1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return results;
|
|
48
162
|
};
|
|
49
163
|
const buildEpicDescription = (epicKey, title, description, acceptance, relatedDocs) => {
|
|
50
164
|
return [
|
|
@@ -100,7 +214,7 @@ const buildStoryDescription = (storyKey, title, userStory, description, acceptan
|
|
|
100
214
|
formatBullets(relatedDocs, "Docdex handles, OpenAPI endpoints, code modules."),
|
|
101
215
|
].join("\n");
|
|
102
216
|
};
|
|
103
|
-
const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, relatedDocs, dependencies) => {
|
|
217
|
+
const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, relatedDocs, dependencies, tests) => {
|
|
104
218
|
return [
|
|
105
219
|
`* **Task Key**: ${taskKey}`,
|
|
106
220
|
"* **Objective**",
|
|
@@ -117,7 +231,10 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
117
231
|
"* **Definition of Done**",
|
|
118
232
|
"- Tests passing, docs updated, review/QA complete.",
|
|
119
233
|
"* **Testing & QA**",
|
|
120
|
-
|
|
234
|
+
`- Unit tests: ${formatTestList(tests.unitTests)}`,
|
|
235
|
+
`- Component tests: ${formatTestList(tests.componentTests)}`,
|
|
236
|
+
`- Integration tests: ${formatTestList(tests.integrationTests)}`,
|
|
237
|
+
`- API tests: ${formatTestList(tests.apiTests)}`,
|
|
121
238
|
"* **Dependencies**",
|
|
122
239
|
formatBullets(dependencies, "Enumerate prerequisite tasks by key."),
|
|
123
240
|
"* **Risks & Gotchas**",
|
|
@@ -181,7 +298,11 @@ const TASK_SCHEMA_SNIPPET = `{
|
|
|
181
298
|
"estimatedStoryPoints": 3,
|
|
182
299
|
"priorityHint": 50,
|
|
183
300
|
"dependsOnKeys": ["t0"],
|
|
184
|
-
"relatedDocs": ["docdex:..."]
|
|
301
|
+
"relatedDocs": ["docdex:..."],
|
|
302
|
+
"unitTests": ["unit test description"],
|
|
303
|
+
"componentTests": ["component test description"],
|
|
304
|
+
"integrationTests": ["integration test description"],
|
|
305
|
+
"apiTests": ["api test description"]
|
|
185
306
|
}
|
|
186
307
|
]
|
|
187
308
|
}`;
|
|
@@ -194,6 +315,7 @@ export class CreateTasksService {
|
|
|
194
315
|
this.repo = deps.repo;
|
|
195
316
|
this.workspaceRepo = deps.workspaceRepo;
|
|
196
317
|
this.routingService = deps.routingService;
|
|
318
|
+
this.ratingService = deps.ratingService;
|
|
197
319
|
}
|
|
198
320
|
static async create(workspace) {
|
|
199
321
|
const repo = await GlobalRepository.create();
|
|
@@ -240,9 +362,23 @@ export class CreateTasksService {
|
|
|
240
362
|
});
|
|
241
363
|
return resolved.agent;
|
|
242
364
|
}
|
|
365
|
+
ensureRatingService() {
|
|
366
|
+
if (!this.ratingService) {
|
|
367
|
+
this.ratingService = new AgentRatingService(this.workspace, {
|
|
368
|
+
workspaceRepo: this.workspaceRepo,
|
|
369
|
+
globalRepo: this.repo,
|
|
370
|
+
agentService: this.agentService,
|
|
371
|
+
routingService: this.routingService,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return this.ratingService;
|
|
375
|
+
}
|
|
243
376
|
async prepareDocs(inputs) {
|
|
377
|
+
const resolvedInputs = inputs.length > 0 ? inputs : await this.resolveDefaultDocInputs();
|
|
378
|
+
if (resolvedInputs.length === 0)
|
|
379
|
+
return [];
|
|
244
380
|
const documents = [];
|
|
245
|
-
for (const input of
|
|
381
|
+
for (const input of resolvedInputs) {
|
|
246
382
|
if (input.startsWith("docdex:")) {
|
|
247
383
|
const docId = input.replace(/^docdex:/, "");
|
|
248
384
|
try {
|
|
@@ -263,6 +399,11 @@ export class CreateTasksService {
|
|
|
263
399
|
throw new Error(`Failed to read input ${input}: ${error.message}`);
|
|
264
400
|
}
|
|
265
401
|
for (const filePath of paths) {
|
|
402
|
+
const baseName = path.basename(filePath);
|
|
403
|
+
if (baseName.endsWith(".meta.json") || baseName.endsWith("-first-draft.md"))
|
|
404
|
+
continue;
|
|
405
|
+
if (!/\.(md|markdown|ya?ml|json)$/i.test(baseName))
|
|
406
|
+
continue;
|
|
266
407
|
const docType = inferDocType(filePath);
|
|
267
408
|
try {
|
|
268
409
|
const doc = await this.docdex.ensureRegisteredFromFile(filePath, docType, {
|
|
@@ -277,6 +418,28 @@ export class CreateTasksService {
|
|
|
277
418
|
}
|
|
278
419
|
return documents;
|
|
279
420
|
}
|
|
421
|
+
async resolveDefaultDocInputs() {
|
|
422
|
+
const candidates = [
|
|
423
|
+
path.join(this.workspace.mcodaDir, "docs"),
|
|
424
|
+
path.join(this.workspace.workspaceRoot, "docs"),
|
|
425
|
+
path.join(this.workspace.workspaceRoot, "openapi"),
|
|
426
|
+
path.join(this.workspace.workspaceRoot, "openapi.yaml"),
|
|
427
|
+
path.join(this.workspace.workspaceRoot, "openapi.yml"),
|
|
428
|
+
path.join(this.workspace.workspaceRoot, "openapi.json"),
|
|
429
|
+
];
|
|
430
|
+
const existing = [];
|
|
431
|
+
for (const candidate of candidates) {
|
|
432
|
+
try {
|
|
433
|
+
const stat = await fs.stat(candidate);
|
|
434
|
+
if (stat.isFile() || stat.isDirectory())
|
|
435
|
+
existing.push(candidate);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
// Ignore missing candidates; fall back to empty inputs.
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return existing;
|
|
442
|
+
}
|
|
280
443
|
buildDocContext(docs) {
|
|
281
444
|
const warnings = [];
|
|
282
445
|
const blocks = [];
|
|
@@ -365,6 +528,10 @@ export class CreateTasksService {
|
|
|
365
528
|
estimatedStoryPoints: 1,
|
|
366
529
|
priorityHint: 10,
|
|
367
530
|
relatedDocs: docRefs,
|
|
531
|
+
unitTests: [],
|
|
532
|
+
componentTests: [],
|
|
533
|
+
integrationTests: [],
|
|
534
|
+
apiTests: [],
|
|
368
535
|
},
|
|
369
536
|
{
|
|
370
537
|
localId: "task-2",
|
|
@@ -375,6 +542,10 @@ export class CreateTasksService {
|
|
|
375
542
|
priorityHint: 20,
|
|
376
543
|
dependsOnKeys: ["task-1"],
|
|
377
544
|
relatedDocs: docRefs,
|
|
545
|
+
unitTests: [],
|
|
546
|
+
componentTests: [],
|
|
547
|
+
integrationTests: [],
|
|
548
|
+
apiTests: [],
|
|
378
549
|
},
|
|
379
550
|
],
|
|
380
551
|
},
|
|
@@ -458,11 +629,11 @@ export class CreateTasksService {
|
|
|
458
629
|
return parsed.epics
|
|
459
630
|
.map((epic, idx) => ({
|
|
460
631
|
localId: epic.localId ?? `e${idx + 1}`,
|
|
461
|
-
area: epic.area,
|
|
632
|
+
area: normalizeArea(epic.area),
|
|
462
633
|
title: epic.title ?? "Epic",
|
|
463
634
|
description: epic.description,
|
|
464
635
|
acceptanceCriteria: Array.isArray(epic.acceptanceCriteria) ? epic.acceptanceCriteria : [],
|
|
465
|
-
relatedDocs:
|
|
636
|
+
relatedDocs: normalizeRelatedDocs(epic.relatedDocs),
|
|
466
637
|
priorityHint: typeof epic.priorityHint === "number" ? epic.priorityHint : undefined,
|
|
467
638
|
stories: [],
|
|
468
639
|
}))
|
|
@@ -496,13 +667,21 @@ export class CreateTasksService {
|
|
|
496
667
|
userStory: story.userStory ?? story.description,
|
|
497
668
|
description: story.description,
|
|
498
669
|
acceptanceCriteria: Array.isArray(story.acceptanceCriteria) ? story.acceptanceCriteria : [],
|
|
499
|
-
relatedDocs:
|
|
670
|
+
relatedDocs: normalizeRelatedDocs(story.relatedDocs),
|
|
500
671
|
priorityHint: typeof story.priorityHint === "number" ? story.priorityHint : undefined,
|
|
501
672
|
tasks: [],
|
|
502
673
|
}))
|
|
503
674
|
.filter((s) => s.title);
|
|
504
675
|
}
|
|
505
676
|
async generateTasksForStory(agent, epic, story, docSummary, stream, jobId, commandRunId) {
|
|
677
|
+
const parseTestList = (value) => {
|
|
678
|
+
if (!Array.isArray(value))
|
|
679
|
+
return [];
|
|
680
|
+
return value
|
|
681
|
+
.filter((item) => typeof item === "string")
|
|
682
|
+
.map((item) => item.trim())
|
|
683
|
+
.filter(Boolean);
|
|
684
|
+
};
|
|
506
685
|
const prompt = [
|
|
507
686
|
`Generate tasks for story "${story.title}" (Epic: ${epic.title}).`,
|
|
508
687
|
"Use the Task template: Objective; Context; Inputs; Implementation Plan; DoD; Testing & QA; Dependencies; Risks; References.",
|
|
@@ -510,6 +689,9 @@ export class CreateTasksService {
|
|
|
510
689
|
TASK_SCHEMA_SNIPPET,
|
|
511
690
|
"Rules:",
|
|
512
691
|
"- Each task must include localId, title, description, type, estimatedStoryPoints, priorityHint.",
|
|
692
|
+
"- Include test arrays: unitTests, componentTests, integrationTests, apiTests. Use [] when not applicable.",
|
|
693
|
+
"- Only include tests that are relevant to the task's scope.",
|
|
694
|
+
"- If the task involves code or configuration changes, include at least one relevant test; do not leave all test arrays empty unless it's purely documentation or research.",
|
|
513
695
|
"- dependsOnKeys must reference localIds in this story.",
|
|
514
696
|
"- Use docdex handles when citing docs.",
|
|
515
697
|
`Story context (key=${story.key ?? story.localId ?? "TBD"}):`,
|
|
@@ -526,16 +708,33 @@ export class CreateTasksService {
|
|
|
526
708
|
throw new Error(`Agent did not return tasks for story ${story.title}`);
|
|
527
709
|
}
|
|
528
710
|
return parsed.tasks
|
|
529
|
-
.map((task, idx) =>
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
711
|
+
.map((task, idx) => {
|
|
712
|
+
const unitTests = parseTestList(task.unitTests);
|
|
713
|
+
const componentTests = parseTestList(task.componentTests);
|
|
714
|
+
const integrationTests = parseTestList(task.integrationTests);
|
|
715
|
+
const apiTests = parseTestList(task.apiTests);
|
|
716
|
+
const hasTests = unitTests.length || componentTests.length || integrationTests.length || apiTests.length;
|
|
717
|
+
const title = task.title ?? "Task";
|
|
718
|
+
const description = task.description ?? "";
|
|
719
|
+
const docOnly = /doc|documentation|readme|pdr|sds|openapi|spec/.test(`${title} ${description}`.toLowerCase());
|
|
720
|
+
if (!hasTests && !docOnly) {
|
|
721
|
+
unitTests.push(`Add tests for ${title} (unit/component/integration/api as applicable)`);
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
localId: task.localId ?? `t${idx + 1}`,
|
|
725
|
+
title,
|
|
726
|
+
type: normalizeTaskType(task.type) ?? "feature",
|
|
727
|
+
description,
|
|
728
|
+
estimatedStoryPoints: typeof task.estimatedStoryPoints === "number" ? task.estimatedStoryPoints : undefined,
|
|
729
|
+
priorityHint: typeof task.priorityHint === "number" ? task.priorityHint : undefined,
|
|
730
|
+
dependsOnKeys: Array.isArray(task.dependsOnKeys) ? task.dependsOnKeys : [],
|
|
731
|
+
relatedDocs: normalizeRelatedDocs(task.relatedDocs),
|
|
732
|
+
unitTests,
|
|
733
|
+
componentTests,
|
|
734
|
+
integrationTests,
|
|
735
|
+
apiTests,
|
|
736
|
+
};
|
|
737
|
+
})
|
|
539
738
|
.filter((t) => t.title);
|
|
540
739
|
}
|
|
541
740
|
async generatePlanFromAgent(epics, agent, docSummary, options) {
|
|
@@ -673,12 +872,25 @@ export class CreateTasksService {
|
|
|
673
872
|
userStoryId: storyId,
|
|
674
873
|
key: task.key,
|
|
675
874
|
title: task.plan.title ?? `Task ${task.key}`,
|
|
676
|
-
description: buildTaskDescription(task.key, task.plan.title ?? `Task ${task.key}`, task.plan.description, task.storyKey, task.epicKey, task.plan.relatedDocs, depSlugs
|
|
875
|
+
description: buildTaskDescription(task.key, task.plan.title ?? `Task ${task.key}`, task.plan.description, task.storyKey, task.epicKey, task.plan.relatedDocs, depSlugs, {
|
|
876
|
+
unitTests: task.plan.unitTests,
|
|
877
|
+
componentTests: task.plan.componentTests,
|
|
878
|
+
integrationTests: task.plan.integrationTests,
|
|
879
|
+
apiTests: task.plan.apiTests,
|
|
880
|
+
}),
|
|
677
881
|
type: task.plan.type ?? "feature",
|
|
678
882
|
status: "not_started",
|
|
679
883
|
storyPoints: task.plan.estimatedStoryPoints ?? null,
|
|
680
884
|
priority: task.plan.priorityHint ?? (taskInserts.length + 1),
|
|
681
|
-
metadata:
|
|
885
|
+
metadata: {
|
|
886
|
+
doc_links: task.plan.relatedDocs ?? [],
|
|
887
|
+
test_requirements: {
|
|
888
|
+
unit: task.plan.unitTests ?? [],
|
|
889
|
+
component: task.plan.componentTests ?? [],
|
|
890
|
+
integration: task.plan.integrationTests ?? [],
|
|
891
|
+
api: task.plan.apiTests ?? [],
|
|
892
|
+
},
|
|
893
|
+
},
|
|
682
894
|
});
|
|
683
895
|
}
|
|
684
896
|
taskRows = await this.workspaceRepo.insertTasks(taskInserts, false);
|
|
@@ -822,6 +1034,27 @@ export class CreateTasksService {
|
|
|
822
1034
|
},
|
|
823
1035
|
});
|
|
824
1036
|
await this.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
1037
|
+
if (options.rateAgents) {
|
|
1038
|
+
try {
|
|
1039
|
+
const ratingService = this.ensureRatingService();
|
|
1040
|
+
await ratingService.rate({
|
|
1041
|
+
workspace: this.workspace,
|
|
1042
|
+
agentId: agent.id,
|
|
1043
|
+
commandName: "create-tasks",
|
|
1044
|
+
jobId: job.id,
|
|
1045
|
+
commandRunId: commandRun.id,
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
catch (error) {
|
|
1049
|
+
const message = `Agent rating failed: ${error.message ?? String(error)}`;
|
|
1050
|
+
try {
|
|
1051
|
+
await this.jobService.appendLog(job.id, `${message}\n`);
|
|
1052
|
+
}
|
|
1053
|
+
catch {
|
|
1054
|
+
/* ignore rating log failures */
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
825
1058
|
return {
|
|
826
1059
|
jobId: job.id,
|
|
827
1060
|
commandRunId: commandRun.id,
|
|
@@ -5,11 +5,13 @@ import { RefineTasksRequest, RefineTasksResult } from "@mcoda/shared";
|
|
|
5
5
|
import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
6
6
|
import { JobService } from "../jobs/JobService.js";
|
|
7
7
|
import { RoutingService } from "../agents/RoutingService.js";
|
|
8
|
+
import { AgentRatingService } from "../agents/AgentRatingService.js";
|
|
8
9
|
interface RefineTasksOptions extends RefineTasksRequest {
|
|
9
10
|
workspace: WorkspaceResolution;
|
|
10
11
|
storyKey?: string;
|
|
11
12
|
agentName?: string;
|
|
12
13
|
agentStream?: boolean;
|
|
14
|
+
rateAgents?: boolean;
|
|
13
15
|
fromDb?: boolean;
|
|
14
16
|
planInPath?: string;
|
|
15
17
|
planOutPath?: string;
|
|
@@ -27,6 +29,7 @@ export declare class RefineTasksService {
|
|
|
27
29
|
private workspaceRepo;
|
|
28
30
|
private routingService;
|
|
29
31
|
private workspace;
|
|
32
|
+
private ratingService?;
|
|
30
33
|
constructor(workspace: WorkspaceResolution, deps: {
|
|
31
34
|
docdex: DocdexClient;
|
|
32
35
|
jobService: JobService;
|
|
@@ -34,10 +37,12 @@ export declare class RefineTasksService {
|
|
|
34
37
|
repo: GlobalRepository;
|
|
35
38
|
workspaceRepo: WorkspaceRepository;
|
|
36
39
|
routingService: RoutingService;
|
|
40
|
+
ratingService?: AgentRatingService;
|
|
37
41
|
});
|
|
38
42
|
static create(workspace: WorkspaceResolution): Promise<RefineTasksService>;
|
|
39
43
|
close(): Promise<void>;
|
|
40
44
|
private resolveAgent;
|
|
45
|
+
private ensureRatingService;
|
|
41
46
|
private selectTasks;
|
|
42
47
|
private parseTaskKeyParts;
|
|
43
48
|
private ensureTaskExists;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RefineTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/RefineTasksService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAA6C,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC7G,OAAO,EAIL,kBAAkB,EAClB,iBAAiB,EAGlB,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;
|
|
1
|
+
{"version":3,"file":"RefineTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/RefineTasksService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAA6C,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC7G,OAAO,EAIL,kBAAkB,EAClB,iBAAiB,EAGlB,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;AAGrE,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;AAmLD,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;IAoB1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,YAAY;IAS1B,OAAO,CAAC,mBAAmB;YAYb,WAAW;IA4KzB,OAAO,CAAC,iBAAiB;YASX,gBAAgB;IAkK9B,OAAO,CAAC,gBAAgB;YA8BV,aAAa;YAoDb,gBAAgB;YA+BhB,kBAAkB;IAkChC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,iBAAiB;IA8EzB,OAAO,CAAC,WAAW;YA0BL,eAAe;YAoSf,WAAW;IA6FnB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAka3E"}
|