@mcoda/core 0.1.20 → 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 +1 -1
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +101 -19
- package/dist/services/planning/CreateTasksService.d.ts +2 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +107 -6
- 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
|
+
}
|
|
@@ -41,7 +41,7 @@ export interface WorkOnTasksResult {
|
|
|
41
41
|
results: TaskExecutionResult[];
|
|
42
42
|
warnings: string[];
|
|
43
43
|
}
|
|
44
|
-
export type MissingTestsPolicy = "block_job" | "skip_task" | "fail_task";
|
|
44
|
+
export type MissingTestsPolicy = "block_job" | "skip_task" | "fail_task" | "continue_task";
|
|
45
45
|
export type ExecutionContextPolicy = "best_effort" | "require_any" | "require_sds_or_openapi";
|
|
46
46
|
export declare class WorkOnTasksService {
|
|
47
47
|
private workspace;
|
|
@@ -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";
|
|
@@ -833,13 +834,22 @@ const splitFileBlocksByExistence = (fileBlocks, cwd) => {
|
|
|
833
834
|
}
|
|
834
835
|
return { existing, remaining };
|
|
835
836
|
};
|
|
836
|
-
const DEFAULT_MISSING_TESTS_POLICY = "
|
|
837
|
+
const DEFAULT_MISSING_TESTS_POLICY = "continue_task";
|
|
837
838
|
const DEFAULT_EXECUTION_CONTEXT_POLICY = "best_effort";
|
|
838
839
|
const normalizeMissingTestsPolicy = (value) => {
|
|
839
840
|
if (typeof value !== "string")
|
|
840
841
|
return undefined;
|
|
841
842
|
const normalized = value.trim().toLowerCase().replace(/-/g, "_");
|
|
842
|
-
if (normalized === "block_job" ||
|
|
843
|
+
if (normalized === "block_job" ||
|
|
844
|
+
normalized === "skip_task" ||
|
|
845
|
+
normalized === "fail_task" ||
|
|
846
|
+
normalized === "continue_task" ||
|
|
847
|
+
normalized === "continue" ||
|
|
848
|
+
normalized === "allow" ||
|
|
849
|
+
normalized === "warn_task") {
|
|
850
|
+
if (normalized === "continue" || normalized === "allow" || normalized === "warn_task") {
|
|
851
|
+
return "continue_task";
|
|
852
|
+
}
|
|
843
853
|
return normalized;
|
|
844
854
|
}
|
|
845
855
|
return undefined;
|
|
@@ -3489,7 +3499,12 @@ export class WorkOnTasksService {
|
|
|
3489
3499
|
const enforceCommentBacklog = isCommentBacklogEnforced();
|
|
3490
3500
|
const commentBacklogMaxFails = resolveCommentBacklogMaxFails();
|
|
3491
3501
|
const requestedMissingTestsPolicy = normalizeMissingTestsPolicy(request.missingTestsPolicy);
|
|
3492
|
-
const missingTestsPolicy = requestedMissingTestsPolicy ??
|
|
3502
|
+
const missingTestsPolicy = requestedMissingTestsPolicy ??
|
|
3503
|
+
(request.allowMissingTests === true
|
|
3504
|
+
? "continue_task"
|
|
3505
|
+
: request.allowMissingTests === false
|
|
3506
|
+
? "block_job"
|
|
3507
|
+
: DEFAULT_MISSING_TESTS_POLICY);
|
|
3493
3508
|
const requestedExecutionContextPolicy = normalizeExecutionContextPolicy(request.executionContextPolicy);
|
|
3494
3509
|
const executionContextPolicy = requestedExecutionContextPolicy ?? DEFAULT_EXECUTION_CONTEXT_POLICY;
|
|
3495
3510
|
const baseCodaliEnvOverrides = codaliRequired ? buildCodaliEnvOverrides() : {};
|
|
@@ -3675,15 +3690,74 @@ export class WorkOnTasksService {
|
|
|
3675
3690
|
warnings.push(...selection.warnings);
|
|
3676
3691
|
const results = [];
|
|
3677
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
|
+
}
|
|
3678
3753
|
if (missingTestsPolicy === "block_job") {
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
const taskKeys = missingHarnessTasks.map((issue) => issue.taskKey);
|
|
3754
|
+
if (preflightMissingHarnessTasks.length > 0) {
|
|
3755
|
+
const taskKeys = preflightMissingHarnessTasks.map((issue) => issue.taskKey);
|
|
3682
3756
|
await this.checkpoint(job.id, "tests_preflight_blocked", {
|
|
3683
3757
|
reason: "missing_test_harness",
|
|
3684
3758
|
policy: missingTestsPolicy,
|
|
3685
3759
|
taskKeys,
|
|
3686
|
-
issues:
|
|
3760
|
+
issues: preflightMissingHarnessTasks.map((issue) => ({
|
|
3687
3761
|
taskKey: issue.taskKey,
|
|
3688
3762
|
testRequirements: issue.testRequirements,
|
|
3689
3763
|
attemptedCommands: issue.attemptedCommands,
|
|
@@ -4212,18 +4286,26 @@ export class WorkOnTasksService {
|
|
|
4212
4286
|
await emitTaskEndOnce();
|
|
4213
4287
|
throw new Error(`missing_test_harness: ${task.task.key} requires tests but no runnable test commands were found`);
|
|
4214
4288
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4289
|
+
if (missingTestsPolicy === "continue_task") {
|
|
4290
|
+
const warning = `Task ${task.task.key}: tests required but no runnable test harness was found; proceeding without automated tests.` +
|
|
4291
|
+
" Add metadata.tests/testCommands or tests/all.js.";
|
|
4292
|
+
warnings.push(warning);
|
|
4293
|
+
await this.logTask(taskRun.id, "Tests required but no runnable test commands were found; continuing without automated tests due to missing-tests policy.", "tests", { testRequirements, missingTestsPolicy });
|
|
4294
|
+
}
|
|
4295
|
+
else {
|
|
4296
|
+
await this.logTask(taskRun.id, "Tests required but no runnable test commands were found; failing task.", "tests", { testRequirements, missingTestsPolicy });
|
|
4297
|
+
await this.stateService.markFailed(task.task, "tests_not_configured", statusContext);
|
|
4298
|
+
await this.deps.workspaceRepo.updateTaskRun(taskRun.id, {
|
|
4299
|
+
status: "failed",
|
|
4300
|
+
finishedAt: new Date().toISOString(),
|
|
4301
|
+
});
|
|
4302
|
+
setFailureReason("tests_not_configured");
|
|
4303
|
+
results.push({ taskKey: task.task.key, status: "failed", notes: "tests_not_configured" });
|
|
4304
|
+
taskStatus = "failed";
|
|
4305
|
+
await this.deps.jobService.updateJobStatus(job.id, "running", { processedItems: index + 1 });
|
|
4306
|
+
await emitTaskEndOnce();
|
|
4307
|
+
continue taskLoop;
|
|
4308
|
+
}
|
|
4227
4309
|
}
|
|
4228
4310
|
const shouldRunTests = !request.dryRun && (testsRequired ? hasRunnableTests : testCommands.length > 0);
|
|
4229
4311
|
let mergeConflicts = [];
|
|
@@ -105,6 +105,8 @@ export declare class CreateTasksService {
|
|
|
105
105
|
private parseEpics;
|
|
106
106
|
private generateStoriesForEpic;
|
|
107
107
|
private generateTasksForStory;
|
|
108
|
+
private buildFallbackStoryForEpic;
|
|
109
|
+
private buildFallbackTasksForStory;
|
|
108
110
|
private generatePlanFromAgent;
|
|
109
111
|
private writePlanArtifacts;
|
|
110
112
|
private persistPlanToDb;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/CreateTasksService.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"CreateTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/CreateTasksService.ts"],"names":[],"mappings":"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"}
|
|
@@ -2360,6 +2360,71 @@ export class CreateTasksService {
|
|
|
2360
2360
|
})
|
|
2361
2361
|
.filter((t) => t.title);
|
|
2362
2362
|
}
|
|
2363
|
+
buildFallbackStoryForEpic(epic) {
|
|
2364
|
+
const criteria = epic.acceptanceCriteria?.filter(Boolean) ?? [];
|
|
2365
|
+
return {
|
|
2366
|
+
localId: "us-fallback-1",
|
|
2367
|
+
title: `Deliver ${epic.title}`,
|
|
2368
|
+
userStory: `As a delivery team, we need an executable implementation story for ${epic.title}.`,
|
|
2369
|
+
description: [
|
|
2370
|
+
`Deterministic fallback story generated because model output for epic "${epic.title}" could not be parsed reliably.`,
|
|
2371
|
+
"Use SDS and related docs to decompose this story into concrete implementation tasks.",
|
|
2372
|
+
].join("\n"),
|
|
2373
|
+
acceptanceCriteria: criteria.length > 0
|
|
2374
|
+
? criteria
|
|
2375
|
+
: [
|
|
2376
|
+
"Story has actionable implementation tasks.",
|
|
2377
|
+
"Dependencies are explicit and story-scoped.",
|
|
2378
|
+
"Tasks are ready for execution.",
|
|
2379
|
+
],
|
|
2380
|
+
relatedDocs: epic.relatedDocs ?? [],
|
|
2381
|
+
priorityHint: 1,
|
|
2382
|
+
tasks: [],
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
buildFallbackTasksForStory(story) {
|
|
2386
|
+
const criteriaLines = (story.acceptanceCriteria ?? [])
|
|
2387
|
+
.slice(0, 6)
|
|
2388
|
+
.map((criterion) => `- ${criterion}`)
|
|
2389
|
+
.join("\n");
|
|
2390
|
+
return [
|
|
2391
|
+
{
|
|
2392
|
+
localId: "t-fallback-1",
|
|
2393
|
+
title: `Fallback planning for ${story.title}`,
|
|
2394
|
+
type: "chore",
|
|
2395
|
+
description: [
|
|
2396
|
+
`Draft a concrete implementation plan for story "${story.title}" using SDS/OpenAPI context.`,
|
|
2397
|
+
"List exact files/modules to touch and implementation order.",
|
|
2398
|
+
criteriaLines ? `Acceptance criteria to satisfy:\n${criteriaLines}` : "Acceptance criteria: use story definition.",
|
|
2399
|
+
].join("\n"),
|
|
2400
|
+
estimatedStoryPoints: 2,
|
|
2401
|
+
priorityHint: 1,
|
|
2402
|
+
dependsOnKeys: [],
|
|
2403
|
+
relatedDocs: story.relatedDocs ?? [],
|
|
2404
|
+
unitTests: [],
|
|
2405
|
+
componentTests: [],
|
|
2406
|
+
integrationTests: [],
|
|
2407
|
+
apiTests: [],
|
|
2408
|
+
},
|
|
2409
|
+
{
|
|
2410
|
+
localId: "t-fallback-2",
|
|
2411
|
+
title: `Fallback implementation for ${story.title}`,
|
|
2412
|
+
type: "feature",
|
|
2413
|
+
description: [
|
|
2414
|
+
`Implement story "${story.title}" according to the fallback planning task.`,
|
|
2415
|
+
"Ensure done criteria and test requirements are explicitly documented for execution.",
|
|
2416
|
+
].join("\n"),
|
|
2417
|
+
estimatedStoryPoints: 3,
|
|
2418
|
+
priorityHint: 2,
|
|
2419
|
+
dependsOnKeys: ["t-fallback-1"],
|
|
2420
|
+
relatedDocs: story.relatedDocs ?? [],
|
|
2421
|
+
unitTests: [],
|
|
2422
|
+
componentTests: [],
|
|
2423
|
+
integrationTests: [],
|
|
2424
|
+
apiTests: [],
|
|
2425
|
+
},
|
|
2426
|
+
];
|
|
2427
|
+
}
|
|
2363
2428
|
async generatePlanFromAgent(epics, agent, docSummary, options) {
|
|
2364
2429
|
const planEpics = epics.map((epic, idx) => ({
|
|
2365
2430
|
...epic,
|
|
@@ -2367,20 +2432,56 @@ export class CreateTasksService {
|
|
|
2367
2432
|
}));
|
|
2368
2433
|
const planStories = [];
|
|
2369
2434
|
const planTasks = [];
|
|
2435
|
+
const fallbackStoryScopes = new Set();
|
|
2370
2436
|
for (const epic of planEpics) {
|
|
2371
|
-
|
|
2372
|
-
|
|
2437
|
+
let stories = [];
|
|
2438
|
+
let usedFallbackStories = false;
|
|
2439
|
+
try {
|
|
2440
|
+
stories = await this.generateStoriesForEpic(agent, { ...epic }, docSummary, options.projectBuildMethod, options.agentStream, options.jobId, options.commandRunId);
|
|
2441
|
+
}
|
|
2442
|
+
catch (error) {
|
|
2443
|
+
usedFallbackStories = true;
|
|
2444
|
+
await this.jobService.appendLog(options.jobId, `Story generation failed for epic "${epic.title}". Using deterministic fallback story. Reason: ${error.message ?? String(error)}\n`);
|
|
2445
|
+
stories = [this.buildFallbackStoryForEpic(epic)];
|
|
2446
|
+
}
|
|
2447
|
+
let limitedStories = stories.slice(0, options.maxStoriesPerEpic ?? stories.length);
|
|
2448
|
+
if (limitedStories.length === 0) {
|
|
2449
|
+
usedFallbackStories = true;
|
|
2450
|
+
await this.jobService.appendLog(options.jobId, `Story generation returned no stories for epic "${epic.title}". Using deterministic fallback story.\n`);
|
|
2451
|
+
limitedStories = [this.buildFallbackStoryForEpic(epic)];
|
|
2452
|
+
}
|
|
2373
2453
|
limitedStories.forEach((story, idx) => {
|
|
2374
|
-
|
|
2454
|
+
const planStory = {
|
|
2375
2455
|
...story,
|
|
2376
2456
|
localId: story.localId ?? `us${idx + 1}`,
|
|
2377
2457
|
epicLocalId: epic.localId,
|
|
2378
|
-
}
|
|
2458
|
+
};
|
|
2459
|
+
planStories.push(planStory);
|
|
2460
|
+
if (usedFallbackStories) {
|
|
2461
|
+
fallbackStoryScopes.add(this.storyScopeKey(planStory.epicLocalId, planStory.localId));
|
|
2462
|
+
}
|
|
2379
2463
|
});
|
|
2380
2464
|
}
|
|
2381
2465
|
for (const story of planStories) {
|
|
2382
|
-
const
|
|
2383
|
-
|
|
2466
|
+
const storyScope = this.storyScopeKey(story.epicLocalId, story.localId);
|
|
2467
|
+
let tasks = [];
|
|
2468
|
+
if (fallbackStoryScopes.has(storyScope)) {
|
|
2469
|
+
tasks = this.buildFallbackTasksForStory(story);
|
|
2470
|
+
}
|
|
2471
|
+
else {
|
|
2472
|
+
try {
|
|
2473
|
+
tasks = await this.generateTasksForStory(agent, { key: story.epicLocalId, title: story.title }, story, docSummary, options.projectBuildMethod, options.agentStream, options.jobId, options.commandRunId);
|
|
2474
|
+
}
|
|
2475
|
+
catch (error) {
|
|
2476
|
+
await this.jobService.appendLog(options.jobId, `Task generation failed for story "${story.title}" (${storyScope}). Using deterministic fallback tasks. Reason: ${error.message ?? String(error)}\n`);
|
|
2477
|
+
tasks = this.buildFallbackTasksForStory(story);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
let limitedTasks = tasks.slice(0, options.maxTasksPerStory ?? tasks.length);
|
|
2481
|
+
if (limitedTasks.length === 0) {
|
|
2482
|
+
await this.jobService.appendLog(options.jobId, `Task generation returned no tasks for story "${story.title}" (${storyScope}). Using deterministic fallback tasks.\n`);
|
|
2483
|
+
limitedTasks = this.buildFallbackTasksForStory(story).slice(0, options.maxTasksPerStory ?? Number.MAX_SAFE_INTEGER);
|
|
2484
|
+
}
|
|
2384
2485
|
limitedTasks.forEach((task, idx) => {
|
|
2385
2486
|
planTasks.push({
|
|
2386
2487
|
...task,
|
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",
|