@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/api/AgentsApi.d.ts +8 -1
  3. package/dist/api/AgentsApi.d.ts.map +1 -1
  4. package/dist/api/AgentsApi.js +70 -0
  5. package/dist/api/QaTasksApi.d.ts.map +1 -1
  6. package/dist/api/QaTasksApi.js +2 -0
  7. package/dist/api/TasksApi.d.ts.map +1 -1
  8. package/dist/api/TasksApi.js +1 -0
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -0
  12. package/dist/prompts/PdrPrompts.d.ts.map +1 -1
  13. package/dist/prompts/PdrPrompts.js +3 -1
  14. package/dist/prompts/SdsPrompts.d.ts.map +1 -1
  15. package/dist/prompts/SdsPrompts.js +2 -0
  16. package/dist/services/agents/AgentRatingFormula.d.ts +27 -0
  17. package/dist/services/agents/AgentRatingFormula.d.ts.map +1 -0
  18. package/dist/services/agents/AgentRatingFormula.js +45 -0
  19. package/dist/services/agents/AgentRatingService.d.ts +41 -0
  20. package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
  21. package/dist/services/agents/AgentRatingService.js +299 -0
  22. package/dist/services/agents/GatewayAgentService.d.ts +3 -0
  23. package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
  24. package/dist/services/agents/GatewayAgentService.js +68 -24
  25. package/dist/services/agents/GatewayHandoff.d.ts +7 -0
  26. package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
  27. package/dist/services/agents/GatewayHandoff.js +108 -0
  28. package/dist/services/backlog/TaskOrderingService.d.ts +1 -0
  29. package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
  30. package/dist/services/backlog/TaskOrderingService.js +19 -16
  31. package/dist/services/docs/DocsService.d.ts +11 -1
  32. package/dist/services/docs/DocsService.d.ts.map +1 -1
  33. package/dist/services/docs/DocsService.js +240 -52
  34. package/dist/services/execution/GatewayTrioService.d.ts +133 -0
  35. package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
  36. package/dist/services/execution/GatewayTrioService.js +1125 -0
  37. package/dist/services/execution/QaFollowupService.d.ts +1 -0
  38. package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
  39. package/dist/services/execution/QaFollowupService.js +1 -0
  40. package/dist/services/execution/QaProfileService.d.ts +6 -0
  41. package/dist/services/execution/QaProfileService.d.ts.map +1 -1
  42. package/dist/services/execution/QaProfileService.js +165 -3
  43. package/dist/services/execution/QaTasksService.d.ts +18 -0
  44. package/dist/services/execution/QaTasksService.d.ts.map +1 -1
  45. package/dist/services/execution/QaTasksService.js +712 -34
  46. package/dist/services/execution/WorkOnTasksService.d.ts +14 -0
  47. package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
  48. package/dist/services/execution/WorkOnTasksService.js +1497 -240
  49. package/dist/services/openapi/OpenApiService.d.ts +10 -0
  50. package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
  51. package/dist/services/openapi/OpenApiService.js +66 -10
  52. package/dist/services/planning/CreateTasksService.d.ts +6 -0
  53. package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
  54. package/dist/services/planning/CreateTasksService.js +261 -28
  55. package/dist/services/planning/RefineTasksService.d.ts +5 -0
  56. package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
  57. package/dist/services/planning/RefineTasksService.js +184 -35
  58. package/dist/services/review/CodeReviewService.d.ts +14 -0
  59. package/dist/services/review/CodeReviewService.d.ts.map +1 -1
  60. package/dist/services/review/CodeReviewService.js +657 -61
  61. package/dist/services/shared/ProjectGuidance.d.ts +6 -0
  62. package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
  63. package/dist/services/shared/ProjectGuidance.js +21 -0
  64. package/dist/services/tasks/TaskCommentFormatter.d.ts +20 -0
  65. package/dist/services/tasks/TaskCommentFormatter.d.ts.map +1 -0
  66. package/dist/services/tasks/TaskCommentFormatter.js +54 -0
  67. package/dist/workspace/WorkspaceManager.d.ts +4 -0
  68. package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
  69. package/dist/workspace/WorkspaceManager.js +3 -0
  70. 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 fenced = raw.match(/```json([\s\S]*?)```/);
36
- const candidate = fenced ? fenced[1] : raw;
37
- const start = candidate.indexOf("{");
38
- const end = candidate.lastIndexOf("}");
39
- if (start === -1 || end === -1 || end <= start)
40
- return undefined;
41
- const body = candidate.slice(start, end + 1);
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(body);
105
+ return JSON.parse(value);
44
106
  }
45
107
  catch {
46
- return undefined;
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
- "- Unit/integration coverage for changed areas.",
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 inputs) {
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: Array.isArray(epic.relatedDocs) ? epic.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: Array.isArray(story.relatedDocs) ? story.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
- localId: task.localId ?? `t${idx + 1}`,
531
- title: task.title ?? "Task",
532
- type: task.type,
533
- description: task.description,
534
- estimatedStoryPoints: typeof task.estimatedStoryPoints === "number" ? task.estimatedStoryPoints : undefined,
535
- priorityHint: typeof task.priorityHint === "number" ? task.priorityHint : undefined,
536
- dependsOnKeys: Array.isArray(task.dependsOnKeys) ? task.dependsOnKeys : [],
537
- relatedDocs: Array.isArray(task.relatedDocs) ? task.relatedDocs : [],
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: task.plan.relatedDocs ? { doc_links: task.plan.relatedDocs } : undefined,
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;AAG7D,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,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;AAsFD,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;gBAGrC,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;KAChC;WAWU,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAoB1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,YAAY;YASZ,WAAW;IA4KzB,OAAO,CAAC,iBAAiB;YASX,gBAAgB;IAkK9B,OAAO,CAAC,gBAAgB;YA4BV,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;CAuX3E"}
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"}