@mcoda/core 0.1.8 → 0.1.11
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/README.md +2 -2
- package/dist/api/AgentsApi.d.ts +9 -1
- package/dist/api/AgentsApi.d.ts.map +1 -1
- package/dist/api/AgentsApi.js +201 -6
- package/dist/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +6 -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 +9 -1
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +9 -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 +60 -0
- package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingService.js +363 -0
- package/dist/services/agents/GatewayAgentService.d.ts +11 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
- package/dist/services/agents/GatewayAgentService.js +525 -84
- package/dist/services/agents/GatewayHandoff.d.ts +11 -0
- package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
- package/dist/services/agents/GatewayHandoff.js +141 -0
- package/dist/services/agents/RoutingService.d.ts +1 -0
- package/dist/services/agents/RoutingService.d.ts.map +1 -1
- package/dist/services/agents/RoutingService.js +4 -4
- package/dist/services/backlog/BacklogService.d.ts +23 -0
- package/dist/services/backlog/BacklogService.d.ts.map +1 -1
- package/dist/services/backlog/BacklogService.js +62 -7
- package/dist/services/backlog/TaskOrderingHeuristics.d.ts +12 -0
- package/dist/services/backlog/TaskOrderingHeuristics.d.ts.map +1 -0
- package/dist/services/backlog/TaskOrderingHeuristics.js +56 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +17 -4
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +538 -79
- package/dist/services/docs/DocInventory.d.ts +11 -0
- package/dist/services/docs/DocInventory.d.ts.map +1 -0
- package/dist/services/docs/DocInventory.js +230 -0
- package/dist/services/docs/DocgenRunContext.d.ts +59 -0
- package/dist/services/docs/DocgenRunContext.d.ts.map +1 -0
- package/dist/services/docs/DocgenRunContext.js +4 -0
- package/dist/services/docs/DocsService.d.ts +70 -3
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +1930 -89
- package/dist/services/docs/alignment/DocAlignmentGraph.d.ts +23 -0
- package/dist/services/docs/alignment/DocAlignmentGraph.d.ts.map +1 -0
- package/dist/services/docs/alignment/DocAlignmentGraph.js +78 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts +19 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts.map +1 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.js +222 -0
- package/dist/services/docs/patch/DocPatchEngine.d.ts +57 -0
- package/dist/services/docs/patch/DocPatchEngine.d.ts.map +1 -0
- package/dist/services/docs/patch/DocPatchEngine.js +331 -0
- package/dist/services/docs/review/Glossary.d.ts +16 -0
- package/dist/services/docs/review/Glossary.d.ts.map +1 -0
- package/dist/services/docs/review/Glossary.js +47 -0
- package/dist/services/docs/review/ReviewReportRenderer.d.ts +3 -0
- package/dist/services/docs/review/ReviewReportRenderer.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewReportRenderer.js +133 -0
- package/dist/services/docs/review/ReviewReportSchema.d.ts +39 -0
- package/dist/services/docs/review/ReviewReportSchema.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewReportSchema.js +47 -0
- package/dist/services/docs/review/ReviewTypes.d.ts +76 -0
- package/dist/services/docs/review/ReviewTypes.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewTypes.js +94 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts +7 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.js +93 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts +7 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.js +308 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts +8 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.js +278 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts +8 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.js +487 -0
- package/dist/services/docs/review/gates/NoMaybesGate.d.ts +8 -0
- package/dist/services/docs/review/gates/NoMaybesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/NoMaybesGate.js +145 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.js +266 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.js +59 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.js +200 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.js +159 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts +8 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.js +129 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.js +169 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts +10 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.js +261 -0
- package/dist/services/docs/review/gates/RfpConsentGate.d.ts +6 -0
- package/dist/services/docs/review/gates/RfpConsentGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/RfpConsentGate.js +127 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts +7 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.js +173 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.js +196 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.js +89 -0
- package/dist/services/docs/review/gates/SdsOpsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsOpsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsOpsGate.js +162 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.js +166 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.js +273 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.js +203 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts +9 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.js +217 -0
- package/dist/services/docs/review/glossary.json +47 -0
- package/dist/services/estimate/EstimateService.d.ts +2 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -1
- package/dist/services/estimate/EstimateService.js +66 -18
- package/dist/services/estimate/VelocityService.d.ts +4 -0
- package/dist/services/estimate/VelocityService.d.ts.map +1 -1
- package/dist/services/estimate/VelocityService.js +179 -36
- package/dist/services/estimate/types.d.ts +1 -0
- package/dist/services/estimate/types.d.ts.map +1 -1
- package/dist/services/execution/GatewayTrioService.d.ts +200 -0
- package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
- package/dist/services/execution/GatewayTrioService.js +2492 -0
- package/dist/services/execution/QaApiRunner.d.ts +30 -0
- package/dist/services/execution/QaApiRunner.d.ts.map +1 -0
- package/dist/services/execution/QaApiRunner.js +881 -0
- package/dist/services/execution/QaFollowupService.d.ts +2 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
- package/dist/services/execution/QaFollowupService.js +9 -2
- package/dist/services/execution/QaPlanValidator.d.ts +10 -0
- package/dist/services/execution/QaPlanValidator.d.ts.map +1 -0
- package/dist/services/execution/QaPlanValidator.js +128 -0
- package/dist/services/execution/QaProfileService.d.ts +27 -1
- package/dist/services/execution/QaProfileService.d.ts.map +1 -1
- package/dist/services/execution/QaProfileService.js +354 -7
- package/dist/services/execution/QaTasksService.d.ts +59 -1
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +3347 -318
- package/dist/services/execution/QaTestCommandBuilder.d.ts +51 -0
- package/dist/services/execution/QaTestCommandBuilder.d.ts.map +1 -0
- package/dist/services/execution/QaTestCommandBuilder.js +495 -0
- package/dist/services/execution/TaskSelectionService.d.ts +4 -2
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +144 -28
- package/dist/services/execution/TaskStateService.d.ts +19 -6
- package/dist/services/execution/TaskStateService.d.ts.map +1 -1
- package/dist/services/execution/TaskStateService.js +128 -13
- package/dist/services/execution/WorkOnTasksService.d.ts +32 -1
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +4667 -722
- package/dist/services/jobs/JobInsightsService.d.ts +4 -0
- package/dist/services/jobs/JobInsightsService.d.ts.map +1 -1
- package/dist/services/jobs/JobInsightsService.js +51 -5
- package/dist/services/jobs/JobResumeService.d.ts.map +1 -1
- package/dist/services/jobs/JobResumeService.js +23 -10
- package/dist/services/jobs/JobService.d.ts +56 -4
- package/dist/services/jobs/JobService.d.ts.map +1 -1
- package/dist/services/jobs/JobService.js +232 -1
- package/dist/services/openapi/OpenApiService.d.ts +51 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +953 -106
- package/dist/services/planning/CreateTasksService.d.ts +21 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +569 -31
- package/dist/services/planning/RefineTasksService.d.ts +9 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +409 -59
- package/dist/services/review/CodeReviewService.d.ts +18 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +1309 -167
- package/dist/services/review/ReviewNormalizer.d.ts +9 -0
- package/dist/services/review/ReviewNormalizer.d.ts.map +1 -0
- package/dist/services/review/ReviewNormalizer.js +147 -0
- package/dist/services/shared/AuthErrors.d.ts +3 -0
- package/dist/services/shared/AuthErrors.d.ts.map +1 -0
- package/dist/services/shared/AuthErrors.js +17 -0
- package/dist/services/shared/DocdexGuidance.d.ts +7 -0
- package/dist/services/shared/DocdexGuidance.d.ts.map +1 -0
- package/dist/services/shared/DocdexGuidance.js +12 -0
- package/dist/services/shared/ProjectGuidance.d.ts +17 -0
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
- package/dist/services/shared/ProjectGuidance.js +78 -0
- package/dist/services/system/ToolDenylist.d.ts +13 -0
- package/dist/services/system/ToolDenylist.d.ts.map +1 -0
- package/dist/services/system/ToolDenylist.js +85 -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/services/telemetry/TelemetryService.d.ts.map +1 -1
- package/dist/services/telemetry/TelemetryService.js +39 -7
- package/dist/workspace/WorkspaceManager.d.ts +26 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
- package/dist/workspace/WorkspaceManager.js +206 -32
- package/package.json +6 -5
|
@@ -14,11 +14,13 @@ export interface FollowupSuggestion {
|
|
|
14
14
|
testName?: string;
|
|
15
15
|
evidenceUrl?: string;
|
|
16
16
|
artifacts?: string[];
|
|
17
|
+
followupSlug?: string;
|
|
17
18
|
}
|
|
18
19
|
export declare class QaFollowupService {
|
|
19
20
|
private workspaceRepo;
|
|
20
21
|
private workspaceRoot;
|
|
21
22
|
constructor(workspaceRepo: WorkspaceRepository, workspaceRoot: string);
|
|
23
|
+
private get mcodaDir();
|
|
22
24
|
private get cachePath();
|
|
23
25
|
private readCache;
|
|
24
26
|
private writeCache;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QaFollowupService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/QaFollowupService.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,oBAAoB,EACpB,UAAU,EACV,OAAO,EACP,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"QaFollowupService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/QaFollowupService.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,oBAAoB,EACpB,UAAU,EACV,OAAO,EACP,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAgCD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,aAAa;IAAuB,OAAO,CAAC,aAAa;gBAAzD,aAAa,EAAE,mBAAmB,EAAU,aAAa,EAAE,MAAM;IAErF,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,SAAS,GAEpB;YAEa,SAAS;YAST,UAAU;YAKV,kBAAkB;IA+C1B,kBAAkB,CACtB,UAAU,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAC7D,UAAU,EAAE,kBAAkB,GAC7B,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,GAAG;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,UAAU,CAAC,EAAE,oBAAoB,CAAC;QAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;YAiFnG,sBAAsB;CA+ErC"}
|
|
@@ -30,8 +30,11 @@ export class QaFollowupService {
|
|
|
30
30
|
this.workspaceRepo = workspaceRepo;
|
|
31
31
|
this.workspaceRoot = workspaceRoot;
|
|
32
32
|
}
|
|
33
|
+
get mcodaDir() {
|
|
34
|
+
return PathHelper.getWorkspaceDir(this.workspaceRoot);
|
|
35
|
+
}
|
|
33
36
|
get cachePath() {
|
|
34
|
-
return path.join(this.
|
|
37
|
+
return path.join(this.mcodaDir, 'qa-containers.json');
|
|
35
38
|
}
|
|
36
39
|
async readCache() {
|
|
37
40
|
try {
|
|
@@ -94,9 +97,13 @@ export class QaFollowupService {
|
|
|
94
97
|
const keyGen = createTaskKeyGenerator(storyKeyBase, existingKeys);
|
|
95
98
|
const followupKey = keyGen();
|
|
96
99
|
const now = new Date().toISOString();
|
|
100
|
+
const storyPoints = suggestion.storyPoints ?? 1;
|
|
101
|
+
const boundedPoints = Math.min(10, Math.max(1, Math.round(storyPoints)));
|
|
97
102
|
const metadata = {
|
|
98
103
|
tags: ['qa-found', 'auto-created', 'ready-for-ai-dev', 'source=qa', ...(suggestion.tags ?? [])],
|
|
99
104
|
source_task: sourceTask.key,
|
|
105
|
+
complexity: boundedPoints,
|
|
106
|
+
...(suggestion.followupSlug ? { qa_followup_slug: suggestion.followupSlug } : {}),
|
|
100
107
|
...(suggestion.components ? { components: suggestion.components } : {}),
|
|
101
108
|
...(suggestion.docLinks ? { doc_links: suggestion.docLinks } : {}),
|
|
102
109
|
...(suggestion.testName ? { failing_test: suggestion.testName } : {}),
|
|
@@ -123,7 +130,7 @@ export class QaFollowupService {
|
|
|
123
130
|
description,
|
|
124
131
|
type: suggestion.type ?? 'bug',
|
|
125
132
|
status: 'not_started',
|
|
126
|
-
storyPoints:
|
|
133
|
+
storyPoints: boundedPoints,
|
|
127
134
|
priority: suggestion.priority ?? 99,
|
|
128
135
|
metadata,
|
|
129
136
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { QaTaskPlan } from '@mcoda/shared';
|
|
2
|
+
type NormalizedQaPlan = {
|
|
3
|
+
taskProfiles: Record<string, string[]>;
|
|
4
|
+
taskPlans: Record<string, QaTaskPlan>;
|
|
5
|
+
notes?: string;
|
|
6
|
+
warnings: string[];
|
|
7
|
+
};
|
|
8
|
+
export declare const normalizeQaPlanOutput: (value: unknown) => NormalizedQaPlan;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=QaPlanValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QaPlanValidator.d.ts","sourceRoot":"","sources":["../../../src/services/execution/QaPlanValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,UAAU,EAAE,MAAM,eAAe,CAAC;AAGnD,KAAK,gBAAgB,GAAG;IACtB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAwGF,eAAO,MAAM,qBAAqB,GAAI,OAAO,OAAO,KAAG,gBAoCtD,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const toStringList = (value) => {
|
|
2
|
+
if (typeof value === 'string')
|
|
3
|
+
return value.trim() ? [value.trim()] : [];
|
|
4
|
+
if (!Array.isArray(value))
|
|
5
|
+
return [];
|
|
6
|
+
return value
|
|
7
|
+
.filter((entry) => typeof entry === 'string')
|
|
8
|
+
.map((entry) => entry.trim())
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
};
|
|
11
|
+
const ASSERT_TEXT_OK_MARKER = '__MCODA_ASSERT_OK__';
|
|
12
|
+
const buildAssertTextExpression = (params) => {
|
|
13
|
+
const textExpression = params.selector
|
|
14
|
+
? `(() => { const el = document.querySelector(${JSON.stringify(params.selector)}); return el ? (el.innerText || el.textContent || '') : ''; })()`
|
|
15
|
+
: `document.body ? (document.body.innerText || document.body.textContent || '') : ''`;
|
|
16
|
+
const expected = JSON.stringify(params.text);
|
|
17
|
+
const comparison = params.contains ? 'actual.includes(expected)' : 'actual.trim() === expected';
|
|
18
|
+
return `(() => { const actual = ${textExpression}; const expected = ${expected}; const ok = ${comparison}; return ok ? ${JSON.stringify(ASSERT_TEXT_OK_MARKER)} : actual; })()`;
|
|
19
|
+
};
|
|
20
|
+
const normalizeBrowserActions = (entries, warnings, taskKey) => {
|
|
21
|
+
const actions = [];
|
|
22
|
+
const context = taskKey ? ` for ${taskKey}` : '';
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry || typeof entry !== 'object')
|
|
25
|
+
continue;
|
|
26
|
+
const action = entry;
|
|
27
|
+
const rawType = typeof action.type === 'string' ? action.type : '';
|
|
28
|
+
if (rawType === 'assertText' || rawType === 'assert_text') {
|
|
29
|
+
const text = typeof action.text === 'string' ? action.text.trim() : '';
|
|
30
|
+
if (!text) {
|
|
31
|
+
warnings.push(`QA plan browser action ${rawType} missing text${context}.`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const selector = typeof action.selector === 'string' ? action.selector : undefined;
|
|
35
|
+
const contains = typeof action.contains === 'boolean' ? action.contains : true;
|
|
36
|
+
actions.push({
|
|
37
|
+
type: 'script',
|
|
38
|
+
expression: buildAssertTextExpression({ selector, text, contains }),
|
|
39
|
+
expect: ASSERT_TEXT_OK_MARKER,
|
|
40
|
+
});
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
actions.push(action);
|
|
44
|
+
}
|
|
45
|
+
return actions;
|
|
46
|
+
};
|
|
47
|
+
const normalizePlanEntry = (value, warnings, taskKey) => {
|
|
48
|
+
if (!value || typeof value !== 'object')
|
|
49
|
+
return undefined;
|
|
50
|
+
const raw = value;
|
|
51
|
+
const profiles = toStringList(raw.profiles);
|
|
52
|
+
const cliCommands = toStringList(raw.cli?.commands);
|
|
53
|
+
const apiRequests = Array.isArray(raw.api?.requests)
|
|
54
|
+
? raw.api.requests.filter((entry) => typeof entry === 'object' && entry)
|
|
55
|
+
: [];
|
|
56
|
+
const rawBrowserActions = Array.isArray(raw.browser?.actions)
|
|
57
|
+
? raw.browser.actions.filter((entry) => typeof entry === 'object' && entry)
|
|
58
|
+
: [];
|
|
59
|
+
const browserActions = normalizeBrowserActions(rawBrowserActions, warnings, taskKey);
|
|
60
|
+
const stressApi = Array.isArray(raw.stress?.api)
|
|
61
|
+
? raw.stress.api.filter((entry) => typeof entry === 'object' && entry)
|
|
62
|
+
: [];
|
|
63
|
+
const stressBrowser = Array.isArray(raw.stress?.browser)
|
|
64
|
+
? raw.stress.browser.filter((entry) => typeof entry === 'object' && entry)
|
|
65
|
+
: [];
|
|
66
|
+
const plan = {};
|
|
67
|
+
if (profiles.length)
|
|
68
|
+
plan.profiles = profiles;
|
|
69
|
+
if (cliCommands.length)
|
|
70
|
+
plan.cli = { commands: cliCommands };
|
|
71
|
+
if (apiRequests.length || typeof raw.api?.base_url === 'string') {
|
|
72
|
+
plan.api = {
|
|
73
|
+
base_url: typeof raw.api?.base_url === 'string' ? raw.api.base_url : undefined,
|
|
74
|
+
requests: apiRequests,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (browserActions.length || typeof raw.browser?.base_url === 'string') {
|
|
78
|
+
plan.browser = {
|
|
79
|
+
base_url: typeof raw.browser?.base_url === 'string' ? raw.browser.base_url : undefined,
|
|
80
|
+
actions: browserActions.length ? browserActions : undefined,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (stressApi.length || stressBrowser.length) {
|
|
84
|
+
plan.stress = {
|
|
85
|
+
api: stressApi,
|
|
86
|
+
browser: stressBrowser,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return Object.keys(plan).length ? plan : undefined;
|
|
90
|
+
};
|
|
91
|
+
export const normalizeQaPlanOutput = (value) => {
|
|
92
|
+
const warnings = [];
|
|
93
|
+
if (!value || typeof value !== 'object') {
|
|
94
|
+
warnings.push('QA plan output is not an object.');
|
|
95
|
+
return { taskProfiles: {}, taskPlans: {}, warnings };
|
|
96
|
+
}
|
|
97
|
+
const raw = value;
|
|
98
|
+
const taskProfilesRaw = raw.task_profiles ?? raw.taskProfiles;
|
|
99
|
+
const taskPlansRaw = raw.task_plans ?? raw.taskPlans ?? raw.tasks;
|
|
100
|
+
const taskProfiles = {};
|
|
101
|
+
if (taskProfilesRaw && typeof taskProfilesRaw === 'object') {
|
|
102
|
+
for (const [key, entry] of Object.entries(taskProfilesRaw)) {
|
|
103
|
+
const list = toStringList(entry);
|
|
104
|
+
if (list.length)
|
|
105
|
+
taskProfiles[key] = list;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (taskProfilesRaw !== undefined) {
|
|
109
|
+
warnings.push('QA plan task_profiles is not an object.');
|
|
110
|
+
}
|
|
111
|
+
const taskPlans = {};
|
|
112
|
+
if (taskPlansRaw && typeof taskPlansRaw === 'object') {
|
|
113
|
+
for (const [key, entry] of Object.entries(taskPlansRaw)) {
|
|
114
|
+
const normalized = normalizePlanEntry(entry, warnings, key);
|
|
115
|
+
if (normalized)
|
|
116
|
+
taskPlans[key] = normalized;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (taskPlansRaw !== undefined) {
|
|
120
|
+
warnings.push('QA plan task_plans is not an object.');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
taskProfiles,
|
|
124
|
+
taskPlans,
|
|
125
|
+
notes: typeof raw.notes === 'string' ? raw.notes : undefined,
|
|
126
|
+
warnings,
|
|
127
|
+
};
|
|
128
|
+
};
|
|
@@ -4,12 +4,34 @@ export interface QaProfileResolutionOptions {
|
|
|
4
4
|
profileName?: string;
|
|
5
5
|
level?: string;
|
|
6
6
|
defaultLevel?: string;
|
|
7
|
+
runnerPreference?: 'cli' | 'chromium' | 'maestro';
|
|
7
8
|
}
|
|
9
|
+
type QaRunner = 'cli' | 'chromium' | 'maestro';
|
|
8
10
|
export declare class QaProfileService {
|
|
9
11
|
private workspaceRoot;
|
|
10
12
|
private cache?;
|
|
13
|
+
private webInterfaceCache?;
|
|
11
14
|
private routingCache?;
|
|
12
|
-
|
|
15
|
+
private noRepoWrites;
|
|
16
|
+
constructor(workspaceRoot: string, options?: {
|
|
17
|
+
noRepoWrites?: boolean;
|
|
18
|
+
});
|
|
19
|
+
private get mcodaDir();
|
|
20
|
+
private fileExists;
|
|
21
|
+
private readPackageJson;
|
|
22
|
+
private detectWebInterface;
|
|
23
|
+
private detectUiTask;
|
|
24
|
+
private detectMobileTask;
|
|
25
|
+
private resolveRunnerPlan;
|
|
26
|
+
getRunnerPlan(task: TaskRow & {
|
|
27
|
+
metadata?: any;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
runners: QaRunner[];
|
|
30
|
+
hasWebInterface: boolean;
|
|
31
|
+
uiTask: boolean;
|
|
32
|
+
mobileTask: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
private resolveRunnerPreference;
|
|
13
35
|
private get profilePath();
|
|
14
36
|
private get workspaceConfigPath();
|
|
15
37
|
private getConfiguredDefaultProfileName;
|
|
@@ -18,5 +40,9 @@ export declare class QaProfileService {
|
|
|
18
40
|
resolveProfileForTask(task: TaskRow & {
|
|
19
41
|
metadata?: any;
|
|
20
42
|
}, options?: QaProfileResolutionOptions): Promise<QaProfile | undefined>;
|
|
43
|
+
resolveProfilesForTask(task: TaskRow & {
|
|
44
|
+
metadata?: any;
|
|
45
|
+
}, options?: QaProfileResolutionOptions): Promise<QaProfile[]>;
|
|
21
46
|
}
|
|
47
|
+
export {};
|
|
22
48
|
//# sourceMappingURL=QaProfileService.d.ts.map
|
|
@@ -1 +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;
|
|
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;AAI1D,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;CACnD;AAED,KAAK,QAAQ,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAc/C,qBAAa,gBAAgB;IAWf,OAAO,CAAC,aAAa;IAVjC,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,iBAAiB,CAAC,CAAU;IACpC,OAAO,CAAC,YAAY,CAAC,CAKnB;IACF,OAAO,CAAC,YAAY,CAAU;gBAEV,aAAa,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAO;IAInF,OAAO,KAAK,QAAQ,GAEnB;YAEa,UAAU;YASV,eAAe;YAUf,kBAAkB;IAyDhC,OAAO,CAAC,YAAY;IAwGpB,OAAO,CAAC,gBAAgB;YAqBV,iBAAiB;IAezB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC;QAC/D,OAAO,EAAE,QAAQ,EAAE,CAAC;QACpB,eAAe,EAAE,OAAO,CAAC;QACzB,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;YAIY,uBAAuB;IAQrC,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,mBAAmB,GAE9B;YAEa,+BAA+B;YAU/B,gBAAgB;IAwBxB,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IA2BpC,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;IAoGnI,sBAAsB,CAC1B,IAAI,EAAE,OAAO,GAAG;QAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,EAClC,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,SAAS,EAAE,CAAC;CA8DxB"}
|
|
@@ -1,15 +1,263 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { PathHelper } from '@mcoda/shared';
|
|
3
|
+
import { classifyTask } from '../backlog/TaskOrderingHeuristics.js';
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
5
|
+
const DEFAULT_QA_PROFILES = [
|
|
6
|
+
{
|
|
7
|
+
name: 'cli',
|
|
8
|
+
runner: 'cli',
|
|
9
|
+
default: true,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'chromium',
|
|
13
|
+
runner: 'chromium',
|
|
14
|
+
},
|
|
15
|
+
];
|
|
4
16
|
export class QaProfileService {
|
|
5
|
-
constructor(workspaceRoot) {
|
|
17
|
+
constructor(workspaceRoot, options = {}) {
|
|
6
18
|
this.workspaceRoot = workspaceRoot;
|
|
19
|
+
this.noRepoWrites = Boolean(options.noRepoWrites);
|
|
20
|
+
}
|
|
21
|
+
get mcodaDir() {
|
|
22
|
+
return PathHelper.getWorkspaceDir(this.workspaceRoot);
|
|
23
|
+
}
|
|
24
|
+
async fileExists(targetPath) {
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(targetPath);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async readPackageJson() {
|
|
34
|
+
const pkgPath = path.join(this.workspaceRoot, 'package.json');
|
|
35
|
+
try {
|
|
36
|
+
const raw = await fs.readFile(pkgPath, 'utf8');
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async detectWebInterface() {
|
|
44
|
+
if (this.webInterfaceCache !== undefined)
|
|
45
|
+
return this.webInterfaceCache;
|
|
46
|
+
const markers = [
|
|
47
|
+
'client',
|
|
48
|
+
'frontend',
|
|
49
|
+
'web',
|
|
50
|
+
'ui',
|
|
51
|
+
'apps/web',
|
|
52
|
+
'apps/client',
|
|
53
|
+
'packages/web',
|
|
54
|
+
'packages/client',
|
|
55
|
+
'src/public',
|
|
56
|
+
'src/public/index.html',
|
|
57
|
+
'public/index.html',
|
|
58
|
+
'index.html',
|
|
59
|
+
'src/App.tsx',
|
|
60
|
+
'src/main.tsx',
|
|
61
|
+
'src/App.jsx',
|
|
62
|
+
'src/main.jsx',
|
|
63
|
+
'next.config.js',
|
|
64
|
+
'next.config.mjs',
|
|
65
|
+
'vite.config.ts',
|
|
66
|
+
'vite.config.js',
|
|
67
|
+
'svelte.config.js',
|
|
68
|
+
'angular.json',
|
|
69
|
+
'nuxt.config.js',
|
|
70
|
+
'astro.config.mjs',
|
|
71
|
+
'remix.config.js',
|
|
72
|
+
];
|
|
73
|
+
for (const marker of markers) {
|
|
74
|
+
if (await this.fileExists(path.join(this.workspaceRoot, marker))) {
|
|
75
|
+
this.webInterfaceCache = true;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const pkg = await this.readPackageJson();
|
|
80
|
+
const deps = {
|
|
81
|
+
...(pkg?.dependencies ?? {}),
|
|
82
|
+
...(pkg?.devDependencies ?? {}),
|
|
83
|
+
...(pkg?.peerDependencies ?? {}),
|
|
84
|
+
};
|
|
85
|
+
const uiDeps = [
|
|
86
|
+
'react',
|
|
87
|
+
'next',
|
|
88
|
+
'vue',
|
|
89
|
+
'nuxt',
|
|
90
|
+
'svelte',
|
|
91
|
+
'astro',
|
|
92
|
+
'@angular/core',
|
|
93
|
+
'@remix-run/react',
|
|
94
|
+
'solid-js',
|
|
95
|
+
];
|
|
96
|
+
const hasUiDep = uiDeps.some((dep) => typeof deps?.[dep] === 'string');
|
|
97
|
+
this.webInterfaceCache = hasUiDep;
|
|
98
|
+
return hasUiDep;
|
|
99
|
+
}
|
|
100
|
+
detectUiTask(task) {
|
|
101
|
+
const metadata = task.metadata ?? {};
|
|
102
|
+
const files = Array.isArray(metadata.files) ? metadata.files : [];
|
|
103
|
+
const reviewFiles = Array.isArray(metadata.last_review_changed_paths)
|
|
104
|
+
? metadata.last_review_changed_paths
|
|
105
|
+
: [];
|
|
106
|
+
const key = String(task.key ?? '').toLowerCase();
|
|
107
|
+
const isUiKey = key.startsWith('web-') ||
|
|
108
|
+
key.startsWith('ui-') ||
|
|
109
|
+
key.startsWith('fe-') ||
|
|
110
|
+
key.startsWith('ux-') ||
|
|
111
|
+
key.includes('-web-') ||
|
|
112
|
+
key.includes('-ui-') ||
|
|
113
|
+
key.includes('-fe-') ||
|
|
114
|
+
key.includes('-ux-');
|
|
115
|
+
if (isUiKey)
|
|
116
|
+
return true;
|
|
117
|
+
const isBackendKey = key.startsWith('bck-') ||
|
|
118
|
+
key.startsWith('backend-') ||
|
|
119
|
+
key.startsWith('api-') ||
|
|
120
|
+
key.startsWith('ops-') ||
|
|
121
|
+
key.startsWith('infra-') ||
|
|
122
|
+
key.includes('-bck-') ||
|
|
123
|
+
key.includes('-backend-') ||
|
|
124
|
+
key.includes('-api-') ||
|
|
125
|
+
key.includes('-ops-') ||
|
|
126
|
+
key.includes('-infra-');
|
|
127
|
+
const tags = Array.isArray(metadata.tags)
|
|
128
|
+
? metadata.tags.map((tag) => String(tag).toLowerCase())
|
|
129
|
+
: [];
|
|
130
|
+
const tagHints = new Set(['ui', 'ux', 'frontend', 'front-end', 'web', 'client']);
|
|
131
|
+
const hasUiTag = tags.some((tag) => tagHints.has(tag));
|
|
132
|
+
const components = Array.isArray(metadata.components)
|
|
133
|
+
? metadata.components.map((component) => String(component).toLowerCase())
|
|
134
|
+
: [];
|
|
135
|
+
const componentHints = new Set(['ui', 'ux', 'frontend', 'front-end', 'web', 'client']);
|
|
136
|
+
const hasUiComponent = components.some((component) => componentHints.has(component));
|
|
137
|
+
const uiHints = [
|
|
138
|
+
'/ui/',
|
|
139
|
+
'/frontend/',
|
|
140
|
+
'/client/',
|
|
141
|
+
'/web/',
|
|
142
|
+
'/components/',
|
|
143
|
+
'/pages/',
|
|
144
|
+
'/app/',
|
|
145
|
+
'/public/',
|
|
146
|
+
'/styles/',
|
|
147
|
+
];
|
|
148
|
+
const uiExtensions = ['.tsx', '.jsx', '.vue', '.svelte', '.astro', '.html', '.css', '.scss', '.less'];
|
|
149
|
+
const hasUiFileHint = (paths, options = {}) => {
|
|
150
|
+
for (const file of paths) {
|
|
151
|
+
const normalized = String(file).toLowerCase();
|
|
152
|
+
const extensionMatch = uiExtensions.some((ext) => normalized.endsWith(ext));
|
|
153
|
+
if (extensionMatch)
|
|
154
|
+
return true;
|
|
155
|
+
if (options.requireExtension)
|
|
156
|
+
continue;
|
|
157
|
+
if (uiHints.some((hint) => normalized.includes(hint)))
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
const type = typeof task.type === 'string' ? task.type.toLowerCase() : '';
|
|
163
|
+
const hasUiType = ['frontend', 'front-end', 'ui', 'web', 'client'].some((hint) => type.includes(hint));
|
|
164
|
+
if (hasUiTag || hasUiComponent || hasUiType)
|
|
165
|
+
return true;
|
|
166
|
+
if (hasUiFileHint(files, { requireExtension: isBackendKey }))
|
|
167
|
+
return true;
|
|
168
|
+
if (isBackendKey)
|
|
169
|
+
return false;
|
|
170
|
+
if (hasUiFileHint(reviewFiles))
|
|
171
|
+
return true;
|
|
172
|
+
const acceptance = Array.isArray(task.acceptanceCriteria)
|
|
173
|
+
? task.acceptanceCriteria
|
|
174
|
+
: [];
|
|
175
|
+
const text = [task.key, task.title, task.description, task.type, ...acceptance]
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.join(' ')
|
|
178
|
+
.toLowerCase();
|
|
179
|
+
if (text) {
|
|
180
|
+
const uiPhrases = [
|
|
181
|
+
'user interface',
|
|
182
|
+
'front end',
|
|
183
|
+
'frontend',
|
|
184
|
+
'front-end',
|
|
185
|
+
'web ui',
|
|
186
|
+
'web-',
|
|
187
|
+
'ui-',
|
|
188
|
+
'ui ',
|
|
189
|
+
'page',
|
|
190
|
+
'screen',
|
|
191
|
+
'view',
|
|
192
|
+
'component',
|
|
193
|
+
'browser',
|
|
194
|
+
'html',
|
|
195
|
+
'css',
|
|
196
|
+
'responsive',
|
|
197
|
+
'accessibility',
|
|
198
|
+
];
|
|
199
|
+
if (uiPhrases.some((phrase) => text.includes(phrase)))
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
const classification = classifyTask({
|
|
203
|
+
title: task.title,
|
|
204
|
+
description: task.description,
|
|
205
|
+
type: task.type ?? undefined,
|
|
206
|
+
});
|
|
207
|
+
if (classification.stage === 'frontend')
|
|
208
|
+
return true;
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
detectMobileTask(task) {
|
|
212
|
+
const metadata = task.metadata ?? {};
|
|
213
|
+
const tags = Array.isArray(metadata.tags) ? metadata.tags.map((t) => t.toLowerCase()) : [];
|
|
214
|
+
const type = typeof task.type === 'string' ? task.type.toLowerCase() : '';
|
|
215
|
+
const mobileTags = new Set(['mobile', 'ios', 'android', 'maestro', 'react-native', 'rn']);
|
|
216
|
+
if (tags.some((tag) => mobileTags.has(tag)))
|
|
217
|
+
return true;
|
|
218
|
+
if (mobileTags.has(type))
|
|
219
|
+
return true;
|
|
220
|
+
const files = Array.isArray(metadata.files) ? metadata.files : [];
|
|
221
|
+
const reviewFiles = Array.isArray(metadata.last_review_changed_paths)
|
|
222
|
+
? metadata.last_review_changed_paths
|
|
223
|
+
: [];
|
|
224
|
+
const combined = [...files, ...reviewFiles];
|
|
225
|
+
const mobileHints = ['/ios/', '/android/', '/mobile/'];
|
|
226
|
+
for (const file of combined) {
|
|
227
|
+
const normalized = String(file).toLowerCase();
|
|
228
|
+
if (mobileHints.some((hint) => normalized.includes(hint)))
|
|
229
|
+
return true;
|
|
230
|
+
if (normalized.endsWith('.maestro.yml') || normalized.endsWith('.maestro.yaml'))
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
async resolveRunnerPlan(task) {
|
|
236
|
+
const hasWebInterface = await this.detectWebInterface();
|
|
237
|
+
const uiTask = this.detectUiTask(task);
|
|
238
|
+
const mobileTask = this.detectMobileTask(task);
|
|
239
|
+
const runners = ['cli'];
|
|
240
|
+
if (uiTask && hasWebInterface)
|
|
241
|
+
runners.push('chromium');
|
|
242
|
+
if (mobileTask)
|
|
243
|
+
runners.push('maestro');
|
|
244
|
+
return { runners, hasWebInterface, uiTask, mobileTask };
|
|
245
|
+
}
|
|
246
|
+
async getRunnerPlan(task) {
|
|
247
|
+
return this.resolveRunnerPlan(task);
|
|
248
|
+
}
|
|
249
|
+
async resolveRunnerPreference(task) {
|
|
250
|
+
if (task && this.detectUiTask(task)) {
|
|
251
|
+
const hasUi = await this.detectWebInterface();
|
|
252
|
+
return hasUi ? 'chromium' : 'cli';
|
|
253
|
+
}
|
|
254
|
+
return 'cli';
|
|
7
255
|
}
|
|
8
256
|
get profilePath() {
|
|
9
|
-
return path.join(this.
|
|
257
|
+
return path.join(this.mcodaDir, 'qa-profiles.json');
|
|
10
258
|
}
|
|
11
259
|
get workspaceConfigPath() {
|
|
12
|
-
return path.join(this.
|
|
260
|
+
return path.join(this.mcodaDir, 'config.json');
|
|
13
261
|
}
|
|
14
262
|
async getConfiguredDefaultProfileName() {
|
|
15
263
|
try {
|
|
@@ -44,7 +292,9 @@ export class QaProfileService {
|
|
|
44
292
|
async loadProfiles() {
|
|
45
293
|
if (this.cache)
|
|
46
294
|
return this.cache;
|
|
47
|
-
|
|
295
|
+
if (!this.noRepoWrites) {
|
|
296
|
+
await PathHelper.ensureDir(this.mcodaDir);
|
|
297
|
+
}
|
|
48
298
|
try {
|
|
49
299
|
const raw = await fs.readFile(this.profilePath, 'utf8');
|
|
50
300
|
const parsed = JSON.parse(raw);
|
|
@@ -56,12 +306,12 @@ export class QaProfileService {
|
|
|
56
306
|
this.cache = parsed.profiles;
|
|
57
307
|
return this.cache;
|
|
58
308
|
}
|
|
59
|
-
this.cache =
|
|
309
|
+
this.cache = DEFAULT_QA_PROFILES;
|
|
60
310
|
return this.cache;
|
|
61
311
|
}
|
|
62
312
|
catch (error) {
|
|
63
313
|
if (error?.code === 'ENOENT') {
|
|
64
|
-
this.cache =
|
|
314
|
+
this.cache = DEFAULT_QA_PROFILES;
|
|
65
315
|
return this.cache;
|
|
66
316
|
}
|
|
67
317
|
throw error;
|
|
@@ -73,6 +323,18 @@ export class QaProfileService {
|
|
|
73
323
|
return undefined;
|
|
74
324
|
const envProfile = process.env.MCODA_QA_PROFILE;
|
|
75
325
|
const routing = await this.getRoutingConfig();
|
|
326
|
+
const runnerPreference = options.runnerPreference ?? (await this.resolveRunnerPreference(task));
|
|
327
|
+
const normalizeRunner = (profile) => profile.runner ?? 'cli';
|
|
328
|
+
const matchRunner = (profile) => normalizeRunner(profile) === runnerPreference || profile.name === runnerPreference;
|
|
329
|
+
const pickByRunner = () => {
|
|
330
|
+
const matches = profiles.filter(matchRunner);
|
|
331
|
+
if (!matches.length)
|
|
332
|
+
return undefined;
|
|
333
|
+
const defaults = matches.filter((p) => p.default);
|
|
334
|
+
if (defaults.length === 1)
|
|
335
|
+
return defaults[0];
|
|
336
|
+
return matches[0];
|
|
337
|
+
};
|
|
76
338
|
const configuredDefault = options.profileName ?? envProfile ?? (await this.getConfiguredDefaultProfileName()) ?? routing.defaultProfile;
|
|
77
339
|
const pickByName = (name) => {
|
|
78
340
|
if (!name)
|
|
@@ -85,6 +347,14 @@ export class QaProfileService {
|
|
|
85
347
|
};
|
|
86
348
|
const explicit = pickByName(configuredDefault);
|
|
87
349
|
if (explicit) {
|
|
350
|
+
if (options.profileName) {
|
|
351
|
+
return explicit;
|
|
352
|
+
}
|
|
353
|
+
if (normalizeRunner(explicit) !== runnerPreference) {
|
|
354
|
+
const fallback = pickByRunner();
|
|
355
|
+
if (fallback)
|
|
356
|
+
return fallback;
|
|
357
|
+
}
|
|
88
358
|
return explicit;
|
|
89
359
|
}
|
|
90
360
|
const taskTags = Array.isArray(task.metadata?.tags)
|
|
@@ -107,8 +377,18 @@ export class QaProfileService {
|
|
|
107
377
|
: undefined;
|
|
108
378
|
const routedName = levelRoute ?? typeRoute ?? tagRoute;
|
|
109
379
|
const routed = pickByName(routedName);
|
|
110
|
-
if (routed)
|
|
380
|
+
if (routed) {
|
|
381
|
+
if (normalizeRunner(routed) !== runnerPreference) {
|
|
382
|
+
const fallback = pickByRunner();
|
|
383
|
+
if (fallback)
|
|
384
|
+
return fallback;
|
|
385
|
+
}
|
|
111
386
|
return routed;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const runnerCandidates = candidates.filter(matchRunner);
|
|
390
|
+
if (runnerCandidates.length) {
|
|
391
|
+
candidates = runnerCandidates;
|
|
112
392
|
}
|
|
113
393
|
const targetLevel = options.level ?? options.defaultLevel;
|
|
114
394
|
if (targetLevel) {
|
|
@@ -131,6 +411,9 @@ export class QaProfileService {
|
|
|
131
411
|
.map((p) => p.name)
|
|
132
412
|
.join(', ')}`);
|
|
133
413
|
}
|
|
414
|
+
const runnerDefault = pickByRunner();
|
|
415
|
+
if (runnerDefault)
|
|
416
|
+
return runnerDefault;
|
|
134
417
|
const defaults = profiles.filter((p) => p.default);
|
|
135
418
|
if (defaults.length === 1)
|
|
136
419
|
return defaults[0];
|
|
@@ -139,4 +422,68 @@ export class QaProfileService {
|
|
|
139
422
|
}
|
|
140
423
|
return undefined;
|
|
141
424
|
}
|
|
425
|
+
async resolveProfilesForTask(task, options = {}) {
|
|
426
|
+
if (options.profileName) {
|
|
427
|
+
const explicit = await this.resolveProfileForTask(task, options);
|
|
428
|
+
return explicit ? [explicit] : [];
|
|
429
|
+
}
|
|
430
|
+
const qaProfiles = Array.isArray(task.metadata?.qa?.profiles_expected)
|
|
431
|
+
? task.metadata.qa.profiles_expected
|
|
432
|
+
.map((value) => String(value).trim())
|
|
433
|
+
.filter(Boolean)
|
|
434
|
+
: [];
|
|
435
|
+
if (qaProfiles.length > 0) {
|
|
436
|
+
const profiles = await this.loadProfiles();
|
|
437
|
+
const byName = new Map(profiles.map((profile) => [profile.name.toLowerCase(), profile]));
|
|
438
|
+
const pickByRunner = (runner) => {
|
|
439
|
+
const matches = profiles.filter((profile) => (profile.runner ?? 'cli') === runner);
|
|
440
|
+
if (!matches.length)
|
|
441
|
+
return undefined;
|
|
442
|
+
const defaults = matches.filter((profile) => profile.default);
|
|
443
|
+
if (defaults.length === 1)
|
|
444
|
+
return defaults[0];
|
|
445
|
+
return matches[0];
|
|
446
|
+
};
|
|
447
|
+
const runnerAliases = {
|
|
448
|
+
api: 'cli',
|
|
449
|
+
cli: 'cli',
|
|
450
|
+
browser: 'chromium',
|
|
451
|
+
web: 'chromium',
|
|
452
|
+
ui: 'chromium',
|
|
453
|
+
chromium: 'chromium',
|
|
454
|
+
maestro: 'maestro',
|
|
455
|
+
mobile: 'maestro',
|
|
456
|
+
};
|
|
457
|
+
const resolved = [];
|
|
458
|
+
const seen = new Set();
|
|
459
|
+
for (const profileName of qaProfiles) {
|
|
460
|
+
const normalized = profileName.toLowerCase();
|
|
461
|
+
let profile = byName.get(normalized);
|
|
462
|
+
if (!profile) {
|
|
463
|
+
const runner = runnerAliases[normalized];
|
|
464
|
+
if (runner) {
|
|
465
|
+
profile = pickByRunner(runner);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (profile && !seen.has(profile.name)) {
|
|
469
|
+
resolved.push(profile);
|
|
470
|
+
seen.add(profile.name);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (resolved.length > 0) {
|
|
474
|
+
return resolved;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const plan = await this.resolveRunnerPlan(task);
|
|
478
|
+
const profiles = [];
|
|
479
|
+
const seen = new Set();
|
|
480
|
+
for (const runner of plan.runners) {
|
|
481
|
+
const profile = await this.resolveProfileForTask(task, { ...options, runnerPreference: runner });
|
|
482
|
+
if (profile && !seen.has(profile.name)) {
|
|
483
|
+
profiles.push(profile);
|
|
484
|
+
seen.add(profile.name);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return profiles;
|
|
488
|
+
}
|
|
142
489
|
}
|