@mcoda/core 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +9 -0
  4. package/dist/api/AgentsApi.d.ts +36 -0
  5. package/dist/api/AgentsApi.d.ts.map +1 -0
  6. package/dist/api/AgentsApi.js +176 -0
  7. package/dist/api/QaTasksApi.d.ts +8 -0
  8. package/dist/api/QaTasksApi.d.ts.map +1 -0
  9. package/dist/api/QaTasksApi.js +36 -0
  10. package/dist/api/TasksApi.d.ts +7 -0
  11. package/dist/api/TasksApi.d.ts.map +1 -0
  12. package/dist/api/TasksApi.js +34 -0
  13. package/dist/config/ConfigService.d.ts +3 -0
  14. package/dist/config/ConfigService.d.ts.map +1 -0
  15. package/dist/config/ConfigService.js +2 -0
  16. package/dist/domain/dependencies/Dependency.d.ts +3 -0
  17. package/dist/domain/dependencies/Dependency.d.ts.map +1 -0
  18. package/dist/domain/dependencies/Dependency.js +2 -0
  19. package/dist/domain/epics/Epic.d.ts +3 -0
  20. package/dist/domain/epics/Epic.d.ts.map +1 -0
  21. package/dist/domain/epics/Epic.js +2 -0
  22. package/dist/domain/projects/Project.d.ts +3 -0
  23. package/dist/domain/projects/Project.d.ts.map +1 -0
  24. package/dist/domain/projects/Project.js +2 -0
  25. package/dist/domain/tasks/Task.d.ts +3 -0
  26. package/dist/domain/tasks/Task.d.ts.map +1 -0
  27. package/dist/domain/tasks/Task.js +2 -0
  28. package/dist/domain/userStories/UserStory.d.ts +3 -0
  29. package/dist/domain/userStories/UserStory.d.ts.map +1 -0
  30. package/dist/domain/userStories/UserStory.js +2 -0
  31. package/dist/index.d.ts +28 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +27 -0
  34. package/dist/prompts/PdrPrompts.d.ts +4 -0
  35. package/dist/prompts/PdrPrompts.d.ts.map +1 -0
  36. package/dist/prompts/PdrPrompts.js +21 -0
  37. package/dist/prompts/PromptLoader.d.ts +3 -0
  38. package/dist/prompts/PromptLoader.d.ts.map +1 -0
  39. package/dist/prompts/PromptLoader.js +2 -0
  40. package/dist/prompts/SdsPrompts.d.ts +5 -0
  41. package/dist/prompts/SdsPrompts.d.ts.map +1 -0
  42. package/dist/prompts/SdsPrompts.js +44 -0
  43. package/dist/services/agents/AgentManagementService.d.ts +3 -0
  44. package/dist/services/agents/AgentManagementService.d.ts.map +1 -0
  45. package/dist/services/agents/AgentManagementService.js +2 -0
  46. package/dist/services/agents/GatewayAgentService.d.ts +92 -0
  47. package/dist/services/agents/GatewayAgentService.d.ts.map +1 -0
  48. package/dist/services/agents/GatewayAgentService.js +870 -0
  49. package/dist/services/agents/RoutingApiClient.d.ts +23 -0
  50. package/dist/services/agents/RoutingApiClient.d.ts.map +1 -0
  51. package/dist/services/agents/RoutingApiClient.js +62 -0
  52. package/dist/services/agents/RoutingService.d.ts +50 -0
  53. package/dist/services/agents/RoutingService.d.ts.map +1 -0
  54. package/dist/services/agents/RoutingService.js +386 -0
  55. package/dist/services/agents/generated/RoutingApiClient.d.ts +21 -0
  56. package/dist/services/agents/generated/RoutingApiClient.d.ts.map +1 -0
  57. package/dist/services/agents/generated/RoutingApiClient.js +68 -0
  58. package/dist/services/backlog/BacklogService.d.ts +98 -0
  59. package/dist/services/backlog/BacklogService.d.ts.map +1 -0
  60. package/dist/services/backlog/BacklogService.js +453 -0
  61. package/dist/services/backlog/TaskOrderingService.d.ts +88 -0
  62. package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -0
  63. package/dist/services/backlog/TaskOrderingService.js +675 -0
  64. package/dist/services/docs/DocsService.d.ts +82 -0
  65. package/dist/services/docs/DocsService.d.ts.map +1 -0
  66. package/dist/services/docs/DocsService.js +1631 -0
  67. package/dist/services/estimate/EstimateService.d.ts +12 -0
  68. package/dist/services/estimate/EstimateService.d.ts.map +1 -0
  69. package/dist/services/estimate/EstimateService.js +103 -0
  70. package/dist/services/estimate/VelocityService.d.ts +19 -0
  71. package/dist/services/estimate/VelocityService.d.ts.map +1 -0
  72. package/dist/services/estimate/VelocityService.js +237 -0
  73. package/dist/services/estimate/types.d.ts +30 -0
  74. package/dist/services/estimate/types.d.ts.map +1 -0
  75. package/dist/services/estimate/types.js +1 -0
  76. package/dist/services/execution/ExecutionService.d.ts +3 -0
  77. package/dist/services/execution/ExecutionService.d.ts.map +1 -0
  78. package/dist/services/execution/ExecutionService.js +2 -0
  79. package/dist/services/execution/QaFollowupService.d.ts +38 -0
  80. package/dist/services/execution/QaFollowupService.d.ts.map +1 -0
  81. package/dist/services/execution/QaFollowupService.js +236 -0
  82. package/dist/services/execution/QaProfileService.d.ts +22 -0
  83. package/dist/services/execution/QaProfileService.d.ts.map +1 -0
  84. package/dist/services/execution/QaProfileService.js +142 -0
  85. package/dist/services/execution/QaTasksService.d.ts +101 -0
  86. package/dist/services/execution/QaTasksService.d.ts.map +1 -0
  87. package/dist/services/execution/QaTasksService.js +1117 -0
  88. package/dist/services/execution/TaskSelectionService.d.ts +50 -0
  89. package/dist/services/execution/TaskSelectionService.d.ts.map +1 -0
  90. package/dist/services/execution/TaskSelectionService.js +281 -0
  91. package/dist/services/execution/TaskStateService.d.ts +19 -0
  92. package/dist/services/execution/TaskStateService.d.ts.map +1 -0
  93. package/dist/services/execution/TaskStateService.js +59 -0
  94. package/dist/services/execution/WorkOnTasksService.d.ts +80 -0
  95. package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -0
  96. package/dist/services/execution/WorkOnTasksService.js +1833 -0
  97. package/dist/services/jobs/JobInsightsService.d.ts +97 -0
  98. package/dist/services/jobs/JobInsightsService.d.ts.map +1 -0
  99. package/dist/services/jobs/JobInsightsService.js +263 -0
  100. package/dist/services/jobs/JobResumeService.d.ts +16 -0
  101. package/dist/services/jobs/JobResumeService.d.ts.map +1 -0
  102. package/dist/services/jobs/JobResumeService.js +113 -0
  103. package/dist/services/jobs/JobService.d.ts +149 -0
  104. package/dist/services/jobs/JobService.d.ts.map +1 -0
  105. package/dist/services/jobs/JobService.js +490 -0
  106. package/dist/services/jobs/JobsApiClient.d.ts +73 -0
  107. package/dist/services/jobs/JobsApiClient.d.ts.map +1 -0
  108. package/dist/services/jobs/JobsApiClient.js +67 -0
  109. package/dist/services/openapi/OpenApiService.d.ts +54 -0
  110. package/dist/services/openapi/OpenApiService.d.ts.map +1 -0
  111. package/dist/services/openapi/OpenApiService.js +503 -0
  112. package/dist/services/planning/CreateTasksService.d.ts +68 -0
  113. package/dist/services/planning/CreateTasksService.d.ts.map +1 -0
  114. package/dist/services/planning/CreateTasksService.js +989 -0
  115. package/dist/services/planning/KeyHelpers.d.ts +5 -0
  116. package/dist/services/planning/KeyHelpers.d.ts.map +1 -0
  117. package/dist/services/planning/KeyHelpers.js +62 -0
  118. package/dist/services/planning/PlanningService.d.ts +3 -0
  119. package/dist/services/planning/PlanningService.d.ts.map +1 -0
  120. package/dist/services/planning/PlanningService.js +2 -0
  121. package/dist/services/planning/RefineTasksService.d.ts +56 -0
  122. package/dist/services/planning/RefineTasksService.d.ts.map +1 -0
  123. package/dist/services/planning/RefineTasksService.js +1328 -0
  124. package/dist/services/review/CodeReviewService.d.ts +103 -0
  125. package/dist/services/review/CodeReviewService.d.ts.map +1 -0
  126. package/dist/services/review/CodeReviewService.js +1187 -0
  127. package/dist/services/system/SystemUpdateService.d.ts +55 -0
  128. package/dist/services/system/SystemUpdateService.d.ts.map +1 -0
  129. package/dist/services/system/SystemUpdateService.js +136 -0
  130. package/dist/services/tasks/TaskApiResolver.d.ts +7 -0
  131. package/dist/services/tasks/TaskApiResolver.d.ts.map +1 -0
  132. package/dist/services/tasks/TaskApiResolver.js +41 -0
  133. package/dist/services/tasks/TaskDetailService.d.ts +106 -0
  134. package/dist/services/tasks/TaskDetailService.d.ts.map +1 -0
  135. package/dist/services/tasks/TaskDetailService.js +332 -0
  136. package/dist/services/telemetry/TelemetryService.d.ts +53 -0
  137. package/dist/services/telemetry/TelemetryService.d.ts.map +1 -0
  138. package/dist/services/telemetry/TelemetryService.js +434 -0
  139. package/dist/workspace/WorkspaceManager.d.ts +35 -0
  140. package/dist/workspace/WorkspaceManager.d.ts.map +1 -0
  141. package/dist/workspace/WorkspaceManager.js +201 -0
  142. package/package.json +45 -0
@@ -0,0 +1,236 @@
1
+ import { createTaskKeyGenerator } from '../planning/KeyHelpers.js';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs/promises';
4
+ import { PathHelper } from '@mcoda/shared';
5
+ const BUG_EPIC_KEY = 'EPIC-BUGS';
6
+ const BUG_STORY_KEY = 'US-BUGS';
7
+ const FOLLOWUP_DESCRIPTION_TEMPLATE = (summary, actual, expected, envLines, steps) => {
8
+ const lines = [
9
+ '# Summary',
10
+ summary || 'Summarize the problem discovered during QA.',
11
+ '',
12
+ '# Steps to Reproduce',
13
+ ...(steps && steps.length
14
+ ? steps.map((step, idx) => `${idx + 1}. ${step}`)
15
+ : ['1. Provide minimal reproducible steps based on failing test or QA notes.']),
16
+ '',
17
+ '# Expected',
18
+ expected || 'Describe the expected behavior per acceptance criteria / OpenAPI.',
19
+ '',
20
+ '# Actual',
21
+ actual || 'Describe the observed behavior.',
22
+ '',
23
+ '# Environment',
24
+ ...envLines.map((line) => `- ${line}`),
25
+ ];
26
+ return lines.join('\n');
27
+ };
28
+ export class QaFollowupService {
29
+ constructor(workspaceRepo, workspaceRoot) {
30
+ this.workspaceRepo = workspaceRepo;
31
+ this.workspaceRoot = workspaceRoot;
32
+ }
33
+ get cachePath() {
34
+ return path.join(this.workspaceRoot, '.mcoda', 'qa-containers.json');
35
+ }
36
+ async readCache() {
37
+ try {
38
+ const raw = await fs.readFile(this.cachePath, 'utf8');
39
+ return JSON.parse(raw);
40
+ }
41
+ catch {
42
+ return {};
43
+ }
44
+ }
45
+ async writeCache(data) {
46
+ await PathHelper.ensureDir(path.dirname(this.cachePath));
47
+ await fs.writeFile(this.cachePath, JSON.stringify(data, null, 2), 'utf8');
48
+ }
49
+ async ensureBugContainer(projectId) {
50
+ const cache = await this.readCache();
51
+ if (cache[projectId]) {
52
+ return cache[projectId];
53
+ }
54
+ let epic = await this.workspaceRepo.getEpicByKey(projectId, BUG_EPIC_KEY);
55
+ if (!epic) {
56
+ const epicInsert = {
57
+ projectId,
58
+ key: BUG_EPIC_KEY,
59
+ title: 'Generic Bugs / Issues',
60
+ description: 'Container epic for QA-found bugs and follow-up items.',
61
+ storyPointsTotal: null,
62
+ priority: 999,
63
+ metadata: { tags: ['generic', 'qa-intake'] },
64
+ };
65
+ const [created] = await this.workspaceRepo.insertEpics([epicInsert], true);
66
+ epic = created;
67
+ }
68
+ let story = await this.workspaceRepo.getStoryByKey(epic.id, BUG_STORY_KEY);
69
+ if (!story) {
70
+ const storyInsert = {
71
+ projectId,
72
+ epicId: epic.id,
73
+ key: BUG_STORY_KEY,
74
+ title: 'Generic Bugs / Issues',
75
+ description: 'Default story container for unmapped QA follow-ups.',
76
+ acceptanceCriteria: undefined,
77
+ storyPointsTotal: null,
78
+ priority: 999,
79
+ metadata: { tags: ['generic', 'qa-intake'] },
80
+ };
81
+ const [created] = await this.workspaceRepo.insertStories([storyInsert], true);
82
+ story = created;
83
+ }
84
+ const resolved = { epicId: epic.id, storyId: story.id, epicKey: epic.key, storyKey: story.key };
85
+ cache[projectId] = resolved;
86
+ await this.writeCache(cache);
87
+ return resolved;
88
+ }
89
+ async createFollowupTask(sourceTask, suggestion) {
90
+ const projectId = sourceTask.projectId;
91
+ const resolved = await this.resolveTargetContainer(sourceTask, suggestion);
92
+ const storyKeyBase = suggestion.storyKeyHint ?? resolved.storyKey ?? sourceTask.storyKey ?? sourceTask.key.split('-t')[0] ?? 'task';
93
+ const existingKeys = await this.workspaceRepo.listTaskKeys(resolved.storyId);
94
+ const keyGen = createTaskKeyGenerator(storyKeyBase, existingKeys);
95
+ const followupKey = keyGen();
96
+ const now = new Date().toISOString();
97
+ const metadata = {
98
+ tags: ['qa-found', 'auto-created', 'ready-for-ai-dev', 'source=qa', ...(suggestion.tags ?? [])],
99
+ source_task: sourceTask.key,
100
+ ...(suggestion.components ? { components: suggestion.components } : {}),
101
+ ...(suggestion.docLinks ? { doc_links: suggestion.docLinks } : {}),
102
+ ...(suggestion.testName ? { failing_test: suggestion.testName } : {}),
103
+ ...(suggestion.evidenceUrl ? { evidence_url: suggestion.evidenceUrl } : {}),
104
+ };
105
+ const envInfo = [
106
+ `Task: ${sourceTask.key}`,
107
+ `Epic: ${sourceTask.epicKey ?? resolved.epicKey ?? sourceTask.epicId}`,
108
+ `Story: ${sourceTask.storyKey ?? resolved.storyKey ?? sourceTask.userStoryId}`,
109
+ `Branch/Commit: ${sourceTask.vcsBranch ?? 'n/a'} / ${sourceTask.vcsLastCommitSha ?? 'n/a'}`,
110
+ `Components: ${Array.isArray(suggestion.components) && suggestion.components.length ? suggestion.components.join(', ') : Array.isArray(sourceTask.metadata?.components) ? sourceTask.metadata.components.join(', ') : 'n/a'}`,
111
+ `Tests: ${suggestion.testName ?? (Array.isArray(sourceTask.metadata?.tests) ? sourceTask.metadata.tests.join(', ') : 'n/a')}`,
112
+ `Docs: ${suggestion.docLinks?.join(', ') ?? (Array.isArray(sourceTask.metadata?.doc_links) ? sourceTask.metadata.doc_links.join(', ') : 'n/a')}`,
113
+ suggestion.evidenceUrl ? `Evidence: ${suggestion.evidenceUrl}` : '',
114
+ suggestion.artifacts && suggestion.artifacts.length ? `Artifacts: ${suggestion.artifacts.join(', ')}` : '',
115
+ ].filter(Boolean);
116
+ const description = FOLLOWUP_DESCRIPTION_TEMPLATE(suggestion.title ?? `Follow-up for ${sourceTask.key}`, suggestion.description ?? 'Actual behavior not provided.', 'Expected behavior per acceptance criteria/OpenAPI.', envInfo);
117
+ const taskInsert = {
118
+ projectId,
119
+ epicId: resolved.epicId,
120
+ userStoryId: resolved.storyId,
121
+ key: followupKey,
122
+ title: suggestion.title || `Follow-up for ${sourceTask.key}`,
123
+ description,
124
+ type: suggestion.type ?? 'bug',
125
+ status: 'not_started',
126
+ storyPoints: suggestion.storyPoints ?? 1,
127
+ priority: suggestion.priority ?? 99,
128
+ metadata,
129
+ };
130
+ const [createdTask] = await this.workspaceRepo.insertTasks([taskInsert], true);
131
+ const dependency = {
132
+ taskId: sourceTask.id,
133
+ dependsOnTaskId: createdTask.id,
134
+ relationType: 'blocks',
135
+ };
136
+ await this.workspaceRepo.insertTaskDependencies([dependency], true);
137
+ const sourceComment = {
138
+ taskId: sourceTask.id,
139
+ sourceCommand: 'qa-tasks',
140
+ authorType: 'agent',
141
+ category: 'qa_followup',
142
+ body: `Created follow-up task ${createdTask.key} for QA findings.`,
143
+ metadata: { followupTaskKey: createdTask.key },
144
+ createdAt: now,
145
+ };
146
+ await this.workspaceRepo.createTaskComment(sourceComment);
147
+ const followupComment = {
148
+ taskId: createdTask.id,
149
+ sourceCommand: 'qa-tasks',
150
+ authorType: 'agent',
151
+ category: 'qa_origin',
152
+ body: `Created automatically from QA of ${sourceTask.key}.`,
153
+ metadata: { sourceTaskKey: sourceTask.key },
154
+ createdAt: now,
155
+ };
156
+ await this.workspaceRepo.createTaskComment(followupComment);
157
+ return { task: { ...taskInsert, id: createdTask.id }, dependency, comment: sourceComment };
158
+ }
159
+ async resolveTargetContainer(sourceTask, suggestion) {
160
+ const projectId = sourceTask.projectId;
161
+ if (suggestion.relatedTaskKey) {
162
+ const related = await this.workspaceRepo.getTaskByKey(suggestion.relatedTaskKey);
163
+ if (related) {
164
+ return {
165
+ epicId: related.epicId,
166
+ storyId: related.userStoryId,
167
+ storyKey: related.key.split('-t')[0] ?? related.key,
168
+ };
169
+ }
170
+ }
171
+ if (suggestion.storyKeyHint) {
172
+ const story = await this.workspaceRepo.getStoryByProjectAndKey(projectId, suggestion.storyKeyHint);
173
+ if (story) {
174
+ return { epicId: story.epicId, storyId: story.id, storyKey: story.key };
175
+ }
176
+ }
177
+ if (Array.isArray(suggestion.docLinks)) {
178
+ for (const link of suggestion.docLinks) {
179
+ const storyKeyCandidate = link.match(/US-[A-Za-z0-9]+/i)?.[0];
180
+ if (storyKeyCandidate) {
181
+ const story = await this.workspaceRepo.getStoryByProjectAndKey(projectId, storyKeyCandidate.toUpperCase());
182
+ if (story)
183
+ return { epicId: story.epicId, storyId: story.id, storyKey: story.key };
184
+ }
185
+ const epicKeyCandidate = link.match(/EPIC-[A-Za-z0-9]+/i)?.[0];
186
+ if (epicKeyCandidate) {
187
+ const epic = await this.workspaceRepo.getEpicByKey(projectId, epicKeyCandidate.toUpperCase());
188
+ if (epic) {
189
+ return {
190
+ epicId: epic.id,
191
+ epicKey: epic.key,
192
+ storyId: sourceTask.userStoryId ?? epic.id,
193
+ storyKey: sourceTask.storyKey ?? epic.key,
194
+ };
195
+ }
196
+ }
197
+ }
198
+ }
199
+ const branch = sourceTask.vcsBranch ?? '';
200
+ const storyMatch = branch.match(/US-[A-Za-z0-9]+/i);
201
+ if (storyMatch) {
202
+ const story = await this.workspaceRepo.getStoryByProjectAndKey(projectId, storyMatch[0].toUpperCase());
203
+ if (story) {
204
+ return { epicId: story.epicId, storyId: story.id, storyKey: story.key };
205
+ }
206
+ }
207
+ const epicMatch = branch.match(/EPIC-[A-Za-z0-9]+/i);
208
+ if (epicMatch) {
209
+ const epic = await this.workspaceRepo.getEpicByKey(projectId, epicMatch[0].toUpperCase());
210
+ if (epic) {
211
+ return {
212
+ epicId: epic.id,
213
+ epicKey: epic.key,
214
+ storyId: sourceTask.userStoryId ?? epic.id,
215
+ storyKey: sourceTask.storyKey ?? suggestion.storyKeyHint ?? epic.key,
216
+ };
217
+ }
218
+ }
219
+ if (suggestion.epicKeyHint) {
220
+ const epic = await this.workspaceRepo.getEpicByKey(projectId, suggestion.epicKeyHint);
221
+ if (epic) {
222
+ return {
223
+ epicId: epic.id,
224
+ epicKey: epic.key,
225
+ storyId: sourceTask.userStoryId ?? epic.id,
226
+ storyKey: sourceTask.storyKey ?? suggestion.storyKeyHint ?? epic.key,
227
+ };
228
+ }
229
+ }
230
+ if (sourceTask.epicId && sourceTask.userStoryId) {
231
+ return { epicId: sourceTask.epicId, storyId: sourceTask.userStoryId, storyKey: sourceTask.storyKey, epicKey: sourceTask.epicKey };
232
+ }
233
+ const container = await this.ensureBugContainer(projectId);
234
+ return { epicId: container.epicId, storyId: container.storyId, storyKey: container.storyKey, epicKey: container.epicKey };
235
+ }
236
+ }
@@ -0,0 +1,22 @@
1
+ import { TaskRow } from '@mcoda/db';
2
+ import { QaProfile } from '@mcoda/shared/qa/QaProfile.js';
3
+ export interface QaProfileResolutionOptions {
4
+ profileName?: string;
5
+ level?: string;
6
+ defaultLevel?: string;
7
+ }
8
+ export declare class QaProfileService {
9
+ private workspaceRoot;
10
+ private cache?;
11
+ private routingCache?;
12
+ constructor(workspaceRoot: string);
13
+ private get profilePath();
14
+ private get workspaceConfigPath();
15
+ private getConfiguredDefaultProfileName;
16
+ private getRoutingConfig;
17
+ loadProfiles(): Promise<QaProfile[]>;
18
+ resolveProfileForTask(task: TaskRow & {
19
+ metadata?: any;
20
+ }, options?: QaProfileResolutionOptions): Promise<QaProfile | undefined>;
21
+ }
22
+ //# sourceMappingURL=QaProfileService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QaProfileService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/QaProfileService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG1D,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,gBAAgB;IASf,OAAO,CAAC,aAAa;IARjC,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,YAAY,CAAC,CAKnB;gBAEkB,aAAa,EAAE,MAAM;IAEzC,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,mBAAmB,GAE9B;YAEa,+BAA+B;YAU/B,gBAAgB;IAwBxB,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAyBpC,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,EAAE,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAqE1I"}
@@ -0,0 +1,142 @@
1
+ import path from 'node:path';
2
+ import { PathHelper } from '@mcoda/shared';
3
+ import fs from 'node:fs/promises';
4
+ export class QaProfileService {
5
+ constructor(workspaceRoot) {
6
+ this.workspaceRoot = workspaceRoot;
7
+ }
8
+ get profilePath() {
9
+ return path.join(this.workspaceRoot, '.mcoda', 'qa-profiles.json');
10
+ }
11
+ get workspaceConfigPath() {
12
+ return path.join(this.workspaceRoot, '.mcoda', 'config.json');
13
+ }
14
+ async getConfiguredDefaultProfileName() {
15
+ try {
16
+ const raw = await fs.readFile(this.workspaceConfigPath, 'utf8');
17
+ const parsed = JSON.parse(raw);
18
+ return parsed?.qa?.defaultProfile ?? parsed?.qa?.default_profile ?? parsed?.qaDefaultProfile;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ async getRoutingConfig() {
25
+ if (this.routingCache)
26
+ return this.routingCache;
27
+ try {
28
+ const raw = await fs.readFile(this.workspaceConfigPath, 'utf8');
29
+ const parsed = JSON.parse(raw);
30
+ const qa = parsed?.qa ?? {};
31
+ this.routingCache = {
32
+ defaultProfile: qa.defaultProfile ?? qa.default_profile ?? qa.default,
33
+ levels: qa.routing?.levels ?? qa.routing?.level ?? qa.levelRouting ?? undefined,
34
+ taskTypes: qa.routing?.taskTypes ?? qa.routing?.types ?? qa.typeRouting ?? undefined,
35
+ tags: qa.routing?.tags ?? undefined,
36
+ };
37
+ return this.routingCache;
38
+ }
39
+ catch {
40
+ this.routingCache = {};
41
+ return this.routingCache;
42
+ }
43
+ }
44
+ async loadProfiles() {
45
+ if (this.cache)
46
+ return this.cache;
47
+ await PathHelper.ensureDir(path.join(this.workspaceRoot, '.mcoda'));
48
+ try {
49
+ const raw = await fs.readFile(this.profilePath, 'utf8');
50
+ const parsed = JSON.parse(raw);
51
+ if (Array.isArray(parsed)) {
52
+ this.cache = parsed;
53
+ return this.cache;
54
+ }
55
+ if (Array.isArray(parsed?.profiles)) {
56
+ this.cache = parsed.profiles;
57
+ return this.cache;
58
+ }
59
+ this.cache = [];
60
+ return this.cache;
61
+ }
62
+ catch (error) {
63
+ if (error?.code === 'ENOENT') {
64
+ this.cache = [];
65
+ return this.cache;
66
+ }
67
+ throw error;
68
+ }
69
+ }
70
+ async resolveProfileForTask(task, options = {}) {
71
+ const profiles = await this.loadProfiles();
72
+ if (!profiles.length)
73
+ return undefined;
74
+ const envProfile = process.env.MCODA_QA_PROFILE;
75
+ const routing = await this.getRoutingConfig();
76
+ const configuredDefault = options.profileName ?? envProfile ?? (await this.getConfiguredDefaultProfileName()) ?? routing.defaultProfile;
77
+ const pickByName = (name) => {
78
+ if (!name)
79
+ return undefined;
80
+ const match = profiles.find((p) => p.name === name);
81
+ if (!match) {
82
+ throw new Error(`QA profile not found: ${name}`);
83
+ }
84
+ return match;
85
+ };
86
+ const explicit = pickByName(configuredDefault);
87
+ if (explicit) {
88
+ return explicit;
89
+ }
90
+ const taskTags = Array.isArray(task.metadata?.tags)
91
+ ? task.metadata.tags.map((t) => t.toLowerCase())
92
+ : [];
93
+ let candidates = profiles.filter((profile) => {
94
+ const typeMatch = !profile.matcher?.task_types ||
95
+ profile.matcher.task_types.length === 0 ||
96
+ (task.type ? profile.matcher.task_types.includes(task.type) : false);
97
+ const tagMatch = !profile.matcher?.tags ||
98
+ profile.matcher.tags.length === 0 ||
99
+ profile.matcher.tags.some((tag) => taskTags.includes(tag.toLowerCase()));
100
+ return typeMatch && tagMatch;
101
+ });
102
+ if (routing) {
103
+ const levelRoute = options.level && routing.levels ? routing.levels[options.level] : undefined;
104
+ const typeRoute = task.type && routing.taskTypes ? routing.taskTypes[task.type] : undefined;
105
+ const tagRoute = routing.tags && taskTags.length
106
+ ? taskTags.map((tag) => routing.tags?.[tag]).find((name) => Boolean(name))
107
+ : undefined;
108
+ const routedName = levelRoute ?? typeRoute ?? tagRoute;
109
+ const routed = pickByName(routedName);
110
+ if (routed)
111
+ return routed;
112
+ }
113
+ const targetLevel = options.level ?? options.defaultLevel;
114
+ if (targetLevel) {
115
+ const levelMatches = candidates.filter((p) => (p.level ?? p.qa_level) === targetLevel);
116
+ if (levelMatches.length === 1)
117
+ return levelMatches[0];
118
+ if (levelMatches.length > 1)
119
+ candidates = levelMatches;
120
+ }
121
+ if (candidates.length === 1)
122
+ return candidates[0];
123
+ if (candidates.length > 1) {
124
+ const defaults = candidates.filter((p) => p.default);
125
+ if (defaults.length === 1)
126
+ return defaults[0];
127
+ if (defaults.length > 1) {
128
+ throw new Error('Multiple default QA profiles configured; specify --profile.');
129
+ }
130
+ throw new Error(`Multiple QA profiles match task (${task.key}); please specify --profile. Candidates: ${candidates
131
+ .map((p) => p.name)
132
+ .join(', ')}`);
133
+ }
134
+ const defaults = profiles.filter((p) => p.default);
135
+ if (defaults.length === 1)
136
+ return defaults[0];
137
+ if (defaults.length > 1) {
138
+ throw new Error('Multiple default QA profiles configured; please specify --profile.');
139
+ }
140
+ return undefined;
141
+ }
142
+ }
@@ -0,0 +1,101 @@
1
+ import { WorkspaceRepository } from '@mcoda/db';
2
+ import { WorkspaceResolution } from '../../workspace/WorkspaceManager.js';
3
+ import { JobService } from '../jobs/JobService.js';
4
+ import { TaskSelectionFilters, TaskSelectionPlan, TaskSelectionService } from './TaskSelectionService.js';
5
+ import { TaskStateService } from './TaskStateService.js';
6
+ import { QaProfileService } from './QaProfileService.js';
7
+ import { QaFollowupService } from './QaFollowupService.js';
8
+ import { VcsClient } from '@mcoda/integrations';
9
+ import { AgentService } from '@mcoda/agents';
10
+ import { GlobalRepository } from '@mcoda/db';
11
+ import { DocdexClient } from '@mcoda/integrations';
12
+ import { RoutingService } from '../agents/RoutingService.js';
13
+ export interface QaTasksRequest extends TaskSelectionFilters {
14
+ workspace: WorkspaceResolution;
15
+ mode?: 'auto' | 'manual';
16
+ resumeJobId?: string;
17
+ profileName?: string;
18
+ level?: string;
19
+ testCommand?: string;
20
+ agentName?: string;
21
+ agentStream?: boolean;
22
+ createFollowupTasks?: 'auto' | 'none' | 'prompt';
23
+ dryRun?: boolean;
24
+ result?: 'pass' | 'fail' | 'blocked';
25
+ notes?: string;
26
+ evidenceUrl?: string;
27
+ }
28
+ export interface QaTaskResult {
29
+ taskKey: string;
30
+ outcome: 'pass' | 'fix_required' | 'infra_issue' | 'unclear';
31
+ profile?: string;
32
+ runner?: string;
33
+ artifacts?: string[];
34
+ followups?: string[];
35
+ commentId?: string;
36
+ notes?: string;
37
+ }
38
+ export interface QaTasksResponse {
39
+ jobId: string;
40
+ commandRunId: string;
41
+ selection: TaskSelectionPlan;
42
+ results: QaTaskResult[];
43
+ warnings: string[];
44
+ }
45
+ export declare class QaTasksService {
46
+ private workspace;
47
+ private deps;
48
+ private profileService;
49
+ private selectionService;
50
+ private stateService;
51
+ private followupService;
52
+ private jobService;
53
+ private vcs;
54
+ private agentService?;
55
+ private docdex?;
56
+ private repo?;
57
+ private routingService?;
58
+ private dryRunGuard;
59
+ constructor(workspace: WorkspaceResolution, deps: {
60
+ workspaceRepo: WorkspaceRepository;
61
+ jobService: JobService;
62
+ selectionService?: TaskSelectionService;
63
+ stateService?: TaskStateService;
64
+ profileService?: QaProfileService;
65
+ followupService?: QaFollowupService;
66
+ vcsClient?: VcsClient;
67
+ agentService?: AgentService;
68
+ docdex?: DocdexClient;
69
+ repo?: GlobalRepository;
70
+ routingService?: RoutingService;
71
+ });
72
+ static create(workspace: WorkspaceResolution, options?: {
73
+ noTelemetry?: boolean;
74
+ }): Promise<QaTasksService>;
75
+ close(): Promise<void>;
76
+ private readPromptFiles;
77
+ private loadPrompts;
78
+ private checkpoint;
79
+ private ensureTaskBranch;
80
+ private ensureMcoda;
81
+ private adapterForProfile;
82
+ private mapOutcome;
83
+ private combineOutcome;
84
+ private gatherDocContext;
85
+ private resolveAgent;
86
+ private estimateTokens;
87
+ private extractJsonCandidate;
88
+ private normalizeAgentOutput;
89
+ private interpretResult;
90
+ private createTaskRun;
91
+ private finishTaskRun;
92
+ private logTask;
93
+ private applyStateTransition;
94
+ private buildFollowupSuggestion;
95
+ private toFollowupSuggestion;
96
+ private suggestFollowupsFromAgent;
97
+ private runAuto;
98
+ private runManual;
99
+ run(request: QaTasksRequest): Promise<QaTasksResponse>;
100
+ }
101
+ //# sourceMappingURL=QaTasksService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QaTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/QaTasksService.ts"],"names":[],"mappings":"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"}