@mcoda/core 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/services/execution/AddTestsService.d.ts +48 -0
- package/dist/services/execution/AddTestsService.d.ts.map +1 -0
- package/dist/services/execution/AddTestsService.js +346 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +64 -4
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./services/planning/RefineTasksService.js";
|
|
|
9
9
|
export * from "./services/planning/KeyHelpers.js";
|
|
10
10
|
export * from "./services/execution/TaskSelectionService.js";
|
|
11
11
|
export * from "./services/execution/TaskStateService.js";
|
|
12
|
+
export * from "./services/execution/AddTestsService.js";
|
|
12
13
|
export * from "./services/execution/WorkOnTasksService.js";
|
|
13
14
|
export * from "./services/execution/QaTasksService.js";
|
|
14
15
|
export * from "./services/execution/GatewayTrioService.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0CAA0C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AACxD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0CAA0C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./services/planning/RefineTasksService.js";
|
|
|
9
9
|
export * from "./services/planning/KeyHelpers.js";
|
|
10
10
|
export * from "./services/execution/TaskSelectionService.js";
|
|
11
11
|
export * from "./services/execution/TaskStateService.js";
|
|
12
|
+
export * from "./services/execution/AddTestsService.js";
|
|
12
13
|
export * from "./services/execution/WorkOnTasksService.js";
|
|
13
14
|
export * from "./services/execution/QaTasksService.js";
|
|
14
15
|
export * from "./services/execution/GatewayTrioService.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { WorkspaceRepository } from "@mcoda/db";
|
|
2
|
+
import { VcsClient } from "@mcoda/integrations";
|
|
3
|
+
import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
4
|
+
import { TaskSelectionFilters, TaskSelectionService } from "./TaskSelectionService.js";
|
|
5
|
+
type AddTestsDeps = {
|
|
6
|
+
workspaceRepo: WorkspaceRepository;
|
|
7
|
+
selectionService: TaskSelectionService;
|
|
8
|
+
vcsClient?: VcsClient;
|
|
9
|
+
};
|
|
10
|
+
export interface AddTestsRequest extends TaskSelectionFilters {
|
|
11
|
+
projectKey: string;
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
commit?: boolean;
|
|
14
|
+
baseBranch?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AddTestsResult {
|
|
17
|
+
projectKey: string;
|
|
18
|
+
selectedTaskKeys: string[];
|
|
19
|
+
tasksRequiringTests: string[];
|
|
20
|
+
updatedTaskKeys: string[];
|
|
21
|
+
skippedTaskKeys: string[];
|
|
22
|
+
createdFiles: string[];
|
|
23
|
+
runAllScriptPath?: string;
|
|
24
|
+
runAllCommand?: string;
|
|
25
|
+
branch?: string;
|
|
26
|
+
commitSha?: string;
|
|
27
|
+
warnings: string[];
|
|
28
|
+
}
|
|
29
|
+
export declare class AddTestsService {
|
|
30
|
+
private workspace;
|
|
31
|
+
private readonly workspaceRepo;
|
|
32
|
+
private readonly selectionService;
|
|
33
|
+
private readonly vcs;
|
|
34
|
+
private readonly ownsWorkspaceRepo;
|
|
35
|
+
private readonly ownsSelectionService;
|
|
36
|
+
constructor(workspace: WorkspaceResolution, deps: AddTestsDeps, ownership?: {
|
|
37
|
+
ownsWorkspaceRepo?: boolean;
|
|
38
|
+
ownsSelectionService?: boolean;
|
|
39
|
+
});
|
|
40
|
+
static create(workspace: WorkspaceResolution): Promise<AddTestsService>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
private resolveTaskSelection;
|
|
43
|
+
private resolveTaskCommands;
|
|
44
|
+
private ensureBaseBranchForCommit;
|
|
45
|
+
addTests(request: AddTestsRequest): Promise<AddTestsResult>;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=AddTestsService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddTestsService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/AddTestsService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAA0B,MAAM,2BAA2B,CAAC;AAa/G,KAAK,YAAY,GAAG;IAClB,aAAa,EAAE,mBAAmB,CAAC;IACnC,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA+ID,qBAAa,eAAe;IAQxB,OAAO,CAAC,SAAS;IAPnB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAU;gBAGrC,SAAS,EAAE,mBAAmB,EACtC,IAAI,EAAE,YAAY,EAClB,SAAS,GAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAAO;WASpE,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAUvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAUd,oBAAoB;YAqBpB,mBAAmB;YAoBnB,yBAAyB;IAqBjC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;CAoIlE"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { WorkspaceRepository } from "@mcoda/db";
|
|
4
|
+
import { VcsClient } from "@mcoda/integrations";
|
|
5
|
+
import { PathHelper, WORK_ALLOWED_STATUSES, filterTaskStatuses } from "@mcoda/shared";
|
|
6
|
+
import { QaTestCommandBuilder } from "./QaTestCommandBuilder.js";
|
|
7
|
+
import { TaskSelectionService } from "./TaskSelectionService.js";
|
|
8
|
+
const DEFAULT_BASE_BRANCH = "mcoda-dev";
|
|
9
|
+
const MISSING_HARNESS_BLOCKER = /No runnable test harness discovered/i;
|
|
10
|
+
const FALLBACK_BOOTSTRAP_COMMAND = "node -e \"console.log('mcoda add-tests bootstrap placeholder')\"";
|
|
11
|
+
const normalizeStringArray = (value) => {
|
|
12
|
+
if (!Array.isArray(value))
|
|
13
|
+
return [];
|
|
14
|
+
return value
|
|
15
|
+
.filter((item) => typeof item === "string")
|
|
16
|
+
.map((item) => item.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
};
|
|
19
|
+
const normalizeTestCommands = (value) => {
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
return trimmed ? [trimmed] : [];
|
|
23
|
+
}
|
|
24
|
+
return normalizeStringArray(value);
|
|
25
|
+
};
|
|
26
|
+
const normalizeTestRequirements = (value) => {
|
|
27
|
+
const raw = value && typeof value === "object" ? value : {};
|
|
28
|
+
return {
|
|
29
|
+
unit: normalizeStringArray(raw.unit),
|
|
30
|
+
component: normalizeStringArray(raw.component),
|
|
31
|
+
integration: normalizeStringArray(raw.integration),
|
|
32
|
+
api: normalizeStringArray(raw.api),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const hasTestRequirements = (requirements) => requirements.unit.length > 0 ||
|
|
36
|
+
requirements.component.length > 0 ||
|
|
37
|
+
requirements.integration.length > 0 ||
|
|
38
|
+
requirements.api.length > 0;
|
|
39
|
+
const dedupeCommands = (commands) => {
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const result = [];
|
|
42
|
+
for (const command of commands) {
|
|
43
|
+
const trimmed = command.trim();
|
|
44
|
+
if (!trimmed || seen.has(trimmed))
|
|
45
|
+
continue;
|
|
46
|
+
seen.add(trimmed);
|
|
47
|
+
result.push(trimmed);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
const resolveNodeCommand = () => {
|
|
52
|
+
const override = process.env.NODE_BIN?.trim();
|
|
53
|
+
const resolved = override || (process.platform === "win32" ? "node.exe" : "node");
|
|
54
|
+
return resolved.includes(" ") ? `"${resolved}"` : resolved;
|
|
55
|
+
};
|
|
56
|
+
const quoteShellPath = (value) => (value.includes(" ") ? `"${value}"` : value);
|
|
57
|
+
const buildRunAllTestsCommand = (relativePath) => {
|
|
58
|
+
const normalized = relativePath.split(path.sep).join("/");
|
|
59
|
+
if (normalized.endsWith(".js"))
|
|
60
|
+
return `${resolveNodeCommand()} ${normalized}`;
|
|
61
|
+
if (normalized.endsWith(".ps1")) {
|
|
62
|
+
const shell = process.platform === "win32" ? "powershell" : "pwsh";
|
|
63
|
+
return `${shell} -File ${quoteShellPath(normalized)}`;
|
|
64
|
+
}
|
|
65
|
+
if (normalized.endsWith(".sh"))
|
|
66
|
+
return `bash ${quoteShellPath(normalized)}`;
|
|
67
|
+
if (normalized.startsWith("."))
|
|
68
|
+
return normalized;
|
|
69
|
+
return `./${normalized}`;
|
|
70
|
+
};
|
|
71
|
+
const detectRunAllTestsScript = (workspaceRoot) => {
|
|
72
|
+
const candidates = ["tests/all.js", "tests/all.sh", "tests/all.ps1", "tests/all"];
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
if (fs.existsSync(path.join(workspaceRoot, ...candidate.split("/"))))
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
};
|
|
79
|
+
const detectRunAllTestsCommand = (workspaceRoot) => {
|
|
80
|
+
const script = detectRunAllTestsScript(workspaceRoot);
|
|
81
|
+
if (!script)
|
|
82
|
+
return undefined;
|
|
83
|
+
return buildRunAllTestsCommand(script);
|
|
84
|
+
};
|
|
85
|
+
const pickSeedTestCategory = (requirements) => {
|
|
86
|
+
const order = ["unit", "component", "integration", "api"];
|
|
87
|
+
const active = order.filter((key) => requirements[key].length > 0);
|
|
88
|
+
if (active.length === 1)
|
|
89
|
+
return active[0];
|
|
90
|
+
return "unit";
|
|
91
|
+
};
|
|
92
|
+
const buildRunAllTestsScript = (seedCategory, seedCommands) => {
|
|
93
|
+
const suites = {
|
|
94
|
+
unit: [],
|
|
95
|
+
component: [],
|
|
96
|
+
integration: [],
|
|
97
|
+
api: [],
|
|
98
|
+
};
|
|
99
|
+
suites[seedCategory] = seedCommands;
|
|
100
|
+
return [
|
|
101
|
+
"#!/usr/bin/env node",
|
|
102
|
+
'const { spawnSync } = require("node:child_process");',
|
|
103
|
+
"",
|
|
104
|
+
"// Register test commands per discipline.",
|
|
105
|
+
`const testSuites = ${JSON.stringify(suites, null, 2)};`,
|
|
106
|
+
"",
|
|
107
|
+
'const entries = Object.entries(testSuites).flatMap(([label, commands]) =>',
|
|
108
|
+
" commands.map((command) => ({ label, command }))",
|
|
109
|
+
");",
|
|
110
|
+
"if (!entries.length) {",
|
|
111
|
+
' console.error("No test commands registered in tests/all.js. Add unit/component/integration/api commands.");',
|
|
112
|
+
" process.exit(1);",
|
|
113
|
+
"}",
|
|
114
|
+
"",
|
|
115
|
+
'console.log("MCODA_RUN_ALL_TESTS_START");',
|
|
116
|
+
"let failed = false;",
|
|
117
|
+
"for (const entry of entries) {",
|
|
118
|
+
" const result = spawnSync(entry.command, { shell: true, stdio: \"inherit\" });",
|
|
119
|
+
" const status = typeof result.status === \"number\" ? result.status : 1;",
|
|
120
|
+
" if (status !== 0) failed = true;",
|
|
121
|
+
"}",
|
|
122
|
+
'console.log(`MCODA_RUN_ALL_TESTS_COMPLETE status=${failed ? "failed" : "passed"}`);',
|
|
123
|
+
'console.log("MCODA_RUN_ALL_TESTS_END");',
|
|
124
|
+
"process.exit(failed ? 1 : 0);",
|
|
125
|
+
"",
|
|
126
|
+
].join("\n");
|
|
127
|
+
};
|
|
128
|
+
const stripHarnessBlocker = (metadata) => {
|
|
129
|
+
const rawQa = metadata.qa;
|
|
130
|
+
if (!rawQa || typeof rawQa !== "object")
|
|
131
|
+
return metadata;
|
|
132
|
+
const qa = { ...rawQa };
|
|
133
|
+
const blockers = normalizeStringArray(qa.blockers).filter((entry) => !MISSING_HARNESS_BLOCKER.test(entry));
|
|
134
|
+
if (blockers.length > 0) {
|
|
135
|
+
qa.blockers = blockers;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
delete qa.blockers;
|
|
139
|
+
}
|
|
140
|
+
return { ...metadata, qa };
|
|
141
|
+
};
|
|
142
|
+
export class AddTestsService {
|
|
143
|
+
constructor(workspace, deps, ownership = {}) {
|
|
144
|
+
this.workspace = workspace;
|
|
145
|
+
this.workspaceRepo = deps.workspaceRepo;
|
|
146
|
+
this.selectionService = deps.selectionService;
|
|
147
|
+
this.vcs = deps.vcsClient ?? new VcsClient();
|
|
148
|
+
this.ownsWorkspaceRepo = ownership.ownsWorkspaceRepo === true;
|
|
149
|
+
this.ownsSelectionService = ownership.ownsSelectionService === true;
|
|
150
|
+
}
|
|
151
|
+
static async create(workspace) {
|
|
152
|
+
const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
|
|
153
|
+
const selectionService = new TaskSelectionService(workspace, workspaceRepo);
|
|
154
|
+
return new AddTestsService(workspace, { workspaceRepo, selectionService }, { ownsWorkspaceRepo: true, ownsSelectionService: true });
|
|
155
|
+
}
|
|
156
|
+
async close() {
|
|
157
|
+
if (this.ownsSelectionService) {
|
|
158
|
+
await this.selectionService.close();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (this.ownsWorkspaceRepo) {
|
|
162
|
+
await this.workspaceRepo.close();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async resolveTaskSelection(request) {
|
|
166
|
+
const ignoreStatusFilter = request.taskKeys?.length ? true : request.ignoreStatusFilter;
|
|
167
|
+
const { filtered } = ignoreStatusFilter
|
|
168
|
+
? { filtered: request.statusFilter ?? [] }
|
|
169
|
+
: filterTaskStatuses(request.statusFilter, WORK_ALLOWED_STATUSES, WORK_ALLOWED_STATUSES);
|
|
170
|
+
return this.selectionService.selectTasks({
|
|
171
|
+
projectKey: request.projectKey,
|
|
172
|
+
epicKey: request.epicKey,
|
|
173
|
+
storyKey: request.storyKey,
|
|
174
|
+
taskKeys: request.taskKeys,
|
|
175
|
+
statusFilter: filtered,
|
|
176
|
+
ignoreStatusFilter,
|
|
177
|
+
includeTypes: request.includeTypes,
|
|
178
|
+
excludeTypes: request.excludeTypes,
|
|
179
|
+
limit: request.limit,
|
|
180
|
+
parallel: request.parallel,
|
|
181
|
+
ignoreDependencies: request.ignoreDependencies ?? true,
|
|
182
|
+
missingContextPolicy: request.missingContextPolicy ?? "allow",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async resolveTaskCommands(task) {
|
|
186
|
+
const metadata = (task.task.metadata ?? {});
|
|
187
|
+
const requirements = normalizeTestRequirements(metadata.test_requirements ?? metadata.testRequirements);
|
|
188
|
+
const existingCommands = dedupeCommands(normalizeTestCommands(metadata.tests ?? metadata.testCommands));
|
|
189
|
+
if (!hasTestRequirements(requirements)) {
|
|
190
|
+
return { requirements, existingCommands, discoveredCommands: [] };
|
|
191
|
+
}
|
|
192
|
+
if (existingCommands.length > 0) {
|
|
193
|
+
return { requirements, existingCommands, discoveredCommands: existingCommands };
|
|
194
|
+
}
|
|
195
|
+
const commandBuilder = new QaTestCommandBuilder(this.workspace.workspaceRoot);
|
|
196
|
+
try {
|
|
197
|
+
const plan = await commandBuilder.build({ task: task.task });
|
|
198
|
+
const discoveredCommands = dedupeCommands(plan.commands);
|
|
199
|
+
return { requirements, existingCommands, discoveredCommands };
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return { requirements, existingCommands, discoveredCommands: [] };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async ensureBaseBranchForCommit(baseBranch) {
|
|
206
|
+
const cwd = this.workspace.workspaceRoot;
|
|
207
|
+
const isRepo = await this.vcs.isRepo(cwd);
|
|
208
|
+
if (!isRepo) {
|
|
209
|
+
return { warning: "add-tests commit skipped: workspace is not a git repository." };
|
|
210
|
+
}
|
|
211
|
+
const status = await this.vcs.status(cwd);
|
|
212
|
+
if (status.trim().length > 0) {
|
|
213
|
+
return { warning: "add-tests commit skipped: working tree is dirty before bootstrap." };
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
await this.vcs.ensureBaseBranch(cwd, baseBranch);
|
|
217
|
+
await this.vcs.checkoutBranch(cwd, baseBranch);
|
|
218
|
+
return { branch: baseBranch };
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
warning: `add-tests commit skipped: failed to prepare base branch ${baseBranch} (${error.message}).`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async addTests(request) {
|
|
227
|
+
const warnings = [];
|
|
228
|
+
const createdFiles = [];
|
|
229
|
+
const updatedTaskKeys = [];
|
|
230
|
+
const skippedTaskKeys = [];
|
|
231
|
+
const dryRun = request.dryRun === true;
|
|
232
|
+
const commitEnabled = request.commit !== false && !dryRun;
|
|
233
|
+
const selection = await this.resolveTaskSelection(request);
|
|
234
|
+
const selectedTaskKeys = selection.ordered.map((entry) => entry.task.key);
|
|
235
|
+
warnings.push(...selection.warnings);
|
|
236
|
+
const requiringTests = [];
|
|
237
|
+
for (const entry of selection.ordered) {
|
|
238
|
+
const commands = await this.resolveTaskCommands(entry);
|
|
239
|
+
if (!hasTestRequirements(commands.requirements))
|
|
240
|
+
continue;
|
|
241
|
+
requiringTests.push({ entry, commands });
|
|
242
|
+
}
|
|
243
|
+
const tasksRequiringTests = requiringTests.map((item) => item.entry.task.key);
|
|
244
|
+
if (tasksRequiringTests.length === 0) {
|
|
245
|
+
return {
|
|
246
|
+
projectKey: request.projectKey,
|
|
247
|
+
selectedTaskKeys,
|
|
248
|
+
tasksRequiringTests: [],
|
|
249
|
+
updatedTaskKeys,
|
|
250
|
+
skippedTaskKeys,
|
|
251
|
+
createdFiles,
|
|
252
|
+
warnings,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
let runAllScriptPath = detectRunAllTestsScript(this.workspace.workspaceRoot);
|
|
256
|
+
let runAllCommand = runAllScriptPath ? buildRunAllTestsCommand(runAllScriptPath) : undefined;
|
|
257
|
+
let commitBranch;
|
|
258
|
+
let commitSha;
|
|
259
|
+
const baseBranch = (request.baseBranch ?? this.workspace.config?.branch ?? DEFAULT_BASE_BRANCH).trim() || DEFAULT_BASE_BRANCH;
|
|
260
|
+
const seedRequirements = requiringTests[0]?.commands.requirements ?? {
|
|
261
|
+
unit: [],
|
|
262
|
+
component: [],
|
|
263
|
+
integration: [],
|
|
264
|
+
api: [],
|
|
265
|
+
};
|
|
266
|
+
const discoveredSeedCommands = dedupeCommands(requiringTests.flatMap((item) => item.commands.discoveredCommands));
|
|
267
|
+
const seedCommands = discoveredSeedCommands.length > 0 ? discoveredSeedCommands : [FALLBACK_BOOTSTRAP_COMMAND];
|
|
268
|
+
if (!runAllScriptPath) {
|
|
269
|
+
if (!dryRun) {
|
|
270
|
+
if (commitEnabled) {
|
|
271
|
+
const branchPrep = await this.ensureBaseBranchForCommit(baseBranch);
|
|
272
|
+
if (branchPrep.branch) {
|
|
273
|
+
commitBranch = branchPrep.branch;
|
|
274
|
+
}
|
|
275
|
+
else if (branchPrep.warning) {
|
|
276
|
+
warnings.push(branchPrep.warning);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const scriptPath = path.join(this.workspace.workspaceRoot, "tests", "all.js");
|
|
280
|
+
await PathHelper.ensureDir(path.dirname(scriptPath));
|
|
281
|
+
const seedCategory = pickSeedTestCategory(seedRequirements);
|
|
282
|
+
const contents = buildRunAllTestsScript(seedCategory, seedCommands);
|
|
283
|
+
await fs.promises.writeFile(scriptPath, contents, "utf8");
|
|
284
|
+
createdFiles.push("tests/all.js");
|
|
285
|
+
runAllScriptPath = "tests/all.js";
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
warnings.push("Dry-run: add-tests would create tests/all.js.");
|
|
289
|
+
}
|
|
290
|
+
runAllCommand = buildRunAllTestsCommand("tests/all.js");
|
|
291
|
+
if (discoveredSeedCommands.length === 0) {
|
|
292
|
+
warnings.push("No stack-specific test commands were discovered; created a placeholder run-all harness. Replace tests/all.js commands with real suites.");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const item of requiringTests) {
|
|
296
|
+
const metadata = (item.entry.task.metadata ?? {});
|
|
297
|
+
const existingCommands = item.commands.existingCommands;
|
|
298
|
+
const fallbackCommands = item.commands.discoveredCommands.length > 0
|
|
299
|
+
? item.commands.discoveredCommands
|
|
300
|
+
: runAllCommand
|
|
301
|
+
? [runAllCommand]
|
|
302
|
+
: [];
|
|
303
|
+
const resolvedCommands = dedupeCommands(existingCommands.length > 0 ? existingCommands : fallbackCommands);
|
|
304
|
+
if (resolvedCommands.length === 0) {
|
|
305
|
+
skippedTaskKeys.push(item.entry.task.key);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const nextMetadata = stripHarnessBlocker({
|
|
309
|
+
...metadata,
|
|
310
|
+
tests: resolvedCommands,
|
|
311
|
+
testCommands: resolvedCommands,
|
|
312
|
+
});
|
|
313
|
+
const before = JSON.stringify(metadata);
|
|
314
|
+
const after = JSON.stringify(nextMetadata);
|
|
315
|
+
if (before !== after) {
|
|
316
|
+
if (!dryRun) {
|
|
317
|
+
await this.workspaceRepo.updateTask(item.entry.task.id, { metadata: nextMetadata });
|
|
318
|
+
}
|
|
319
|
+
updatedTaskKeys.push(item.entry.task.key);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (commitEnabled && createdFiles.includes("tests/all.js")) {
|
|
323
|
+
try {
|
|
324
|
+
await this.vcs.stage(this.workspace.workspaceRoot, ["tests/all.js"]);
|
|
325
|
+
await this.vcs.commit(this.workspace.workspaceRoot, "chore(mcoda): bootstrap test harness");
|
|
326
|
+
commitSha = await this.vcs.lastCommitSha(this.workspace.workspaceRoot);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
warnings.push(`add-tests commit failed: ${error.message}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
projectKey: request.projectKey,
|
|
334
|
+
selectedTaskKeys,
|
|
335
|
+
tasksRequiringTests,
|
|
336
|
+
updatedTaskKeys,
|
|
337
|
+
skippedTaskKeys,
|
|
338
|
+
createdFiles,
|
|
339
|
+
runAllScriptPath,
|
|
340
|
+
runAllCommand,
|
|
341
|
+
branch: commitBranch,
|
|
342
|
+
commitSha,
|
|
343
|
+
warnings,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkOnTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/WorkOnTasksService.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAgD,MAAM,eAAe,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAuB,MAAM,WAAW,CAAC;AAEvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAiB,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC1G,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkOnTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/WorkOnTasksService.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ import { JobService } from "../jobs/JobService.js";
|
|
|
12
12
|
import { TaskSelectionService } from "./TaskSelectionService.js";
|
|
13
13
|
import { TaskStateService } from "./TaskStateService.js";
|
|
14
14
|
import { QaTestCommandBuilder } from "./QaTestCommandBuilder.js";
|
|
15
|
+
import { AddTestsService } from "./AddTestsService.js";
|
|
15
16
|
import { RoutingService } from "../agents/RoutingService.js";
|
|
16
17
|
import { GATEWAY_HANDOFF_ENV_PATH } from "../agents/GatewayHandoff.js";
|
|
17
18
|
import { AgentRatingService } from "../agents/AgentRatingService.js";
|
|
@@ -3689,15 +3690,74 @@ export class WorkOnTasksService {
|
|
|
3689
3690
|
warnings.push(...selection.warnings);
|
|
3690
3691
|
const results = [];
|
|
3691
3692
|
const taskSummaries = new Map();
|
|
3693
|
+
let preflightMissingHarnessTasks = await this.findMissingTestHarnessTasks(selection.ordered);
|
|
3694
|
+
if (preflightMissingHarnessTasks.length > 0 && !request.dryRun) {
|
|
3695
|
+
const bootstrapProjectKey = selection.project?.key ?? request.projectKey;
|
|
3696
|
+
if (!bootstrapProjectKey) {
|
|
3697
|
+
warnings.push("add-tests bootstrap skipped: project key could not be resolved.");
|
|
3698
|
+
}
|
|
3699
|
+
else {
|
|
3700
|
+
try {
|
|
3701
|
+
const addTestsService = new AddTestsService(this.workspace, {
|
|
3702
|
+
workspaceRepo: this.deps.workspaceRepo,
|
|
3703
|
+
selectionService: this.selectionService,
|
|
3704
|
+
vcsClient: this.vcs,
|
|
3705
|
+
});
|
|
3706
|
+
const bootstrap = await addTestsService.addTests({
|
|
3707
|
+
projectKey: bootstrapProjectKey,
|
|
3708
|
+
taskKeys: preflightMissingHarnessTasks.map((issue) => issue.taskKey),
|
|
3709
|
+
ignoreStatusFilter: true,
|
|
3710
|
+
ignoreDependencies: true,
|
|
3711
|
+
dryRun: false,
|
|
3712
|
+
commit: !(request.noCommit ?? false),
|
|
3713
|
+
baseBranch,
|
|
3714
|
+
});
|
|
3715
|
+
if (bootstrap.createdFiles.length > 0) {
|
|
3716
|
+
warnings.push(`add-tests bootstrap created: ${bootstrap.createdFiles.join(", ")}`);
|
|
3717
|
+
}
|
|
3718
|
+
if (bootstrap.commitSha) {
|
|
3719
|
+
warnings.push(`add-tests bootstrap commit: ${bootstrap.commitSha}${bootstrap.branch ? ` on ${bootstrap.branch}` : ""}`);
|
|
3720
|
+
}
|
|
3721
|
+
warnings.push(...bootstrap.warnings.map((warning) => `add-tests: ${warning}`));
|
|
3722
|
+
await this.checkpoint(job.id, "tests_bootstrap", {
|
|
3723
|
+
createdFiles: bootstrap.createdFiles,
|
|
3724
|
+
updatedTaskKeys: bootstrap.updatedTaskKeys,
|
|
3725
|
+
skippedTaskKeys: bootstrap.skippedTaskKeys,
|
|
3726
|
+
commitSha: bootstrap.commitSha ?? null,
|
|
3727
|
+
branch: bootstrap.branch ?? null,
|
|
3728
|
+
});
|
|
3729
|
+
const refreshedRows = await this.deps.workspaceRepo.getTasksByIds(selection.ordered.map((entry) => entry.task.id));
|
|
3730
|
+
const refreshedById = new Map(refreshedRows.map((row) => [row.id, row]));
|
|
3731
|
+
selection = {
|
|
3732
|
+
...selection,
|
|
3733
|
+
ordered: selection.ordered.map((entry) => {
|
|
3734
|
+
const refreshed = refreshedById.get(entry.task.id);
|
|
3735
|
+
if (!refreshed)
|
|
3736
|
+
return entry;
|
|
3737
|
+
return {
|
|
3738
|
+
...entry,
|
|
3739
|
+
task: {
|
|
3740
|
+
...entry.task,
|
|
3741
|
+
metadata: refreshed.metadata,
|
|
3742
|
+
},
|
|
3743
|
+
};
|
|
3744
|
+
}),
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
catch (error) {
|
|
3748
|
+
warnings.push(`add-tests bootstrap failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
preflightMissingHarnessTasks = await this.findMissingTestHarnessTasks(selection.ordered);
|
|
3752
|
+
}
|
|
3692
3753
|
if (missingTestsPolicy === "block_job") {
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
const taskKeys = missingHarnessTasks.map((issue) => issue.taskKey);
|
|
3754
|
+
if (preflightMissingHarnessTasks.length > 0) {
|
|
3755
|
+
const taskKeys = preflightMissingHarnessTasks.map((issue) => issue.taskKey);
|
|
3696
3756
|
await this.checkpoint(job.id, "tests_preflight_blocked", {
|
|
3697
3757
|
reason: "missing_test_harness",
|
|
3698
3758
|
policy: missingTestsPolicy,
|
|
3699
3759
|
taskKeys,
|
|
3700
|
-
issues:
|
|
3760
|
+
issues: preflightMissingHarnessTasks.map((issue) => ({
|
|
3701
3761
|
taskKey: issue.taskKey,
|
|
3702
3762
|
testRequirements: issue.testRequirements,
|
|
3703
3763
|
attemptedCommands: issue.attemptedCommands,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcoda/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Core services and APIs for the mcoda CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
34
34
|
"yaml": "^2.4.2",
|
|
35
|
-
"@mcoda/
|
|
36
|
-
"@mcoda/
|
|
37
|
-
"@mcoda/
|
|
38
|
-
"@mcoda/
|
|
39
|
-
"@mcoda/
|
|
35
|
+
"@mcoda/db": "0.1.22",
|
|
36
|
+
"@mcoda/agents": "0.1.22",
|
|
37
|
+
"@mcoda/generators": "0.1.22",
|
|
38
|
+
"@mcoda/integrations": "0.1.22",
|
|
39
|
+
"@mcoda/shared": "0.1.22"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.json",
|