@mcoda/core 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/api/AgentsApi.d.ts +36 -0
- package/dist/api/AgentsApi.d.ts.map +1 -0
- package/dist/api/AgentsApi.js +176 -0
- package/dist/api/QaTasksApi.d.ts +8 -0
- package/dist/api/QaTasksApi.d.ts.map +1 -0
- package/dist/api/QaTasksApi.js +36 -0
- package/dist/api/TasksApi.d.ts +7 -0
- package/dist/api/TasksApi.d.ts.map +1 -0
- package/dist/api/TasksApi.js +34 -0
- package/dist/config/ConfigService.d.ts +3 -0
- package/dist/config/ConfigService.d.ts.map +1 -0
- package/dist/config/ConfigService.js +2 -0
- package/dist/domain/dependencies/Dependency.d.ts +3 -0
- package/dist/domain/dependencies/Dependency.d.ts.map +1 -0
- package/dist/domain/dependencies/Dependency.js +2 -0
- package/dist/domain/epics/Epic.d.ts +3 -0
- package/dist/domain/epics/Epic.d.ts.map +1 -0
- package/dist/domain/epics/Epic.js +2 -0
- package/dist/domain/projects/Project.d.ts +3 -0
- package/dist/domain/projects/Project.d.ts.map +1 -0
- package/dist/domain/projects/Project.js +2 -0
- package/dist/domain/tasks/Task.d.ts +3 -0
- package/dist/domain/tasks/Task.d.ts.map +1 -0
- package/dist/domain/tasks/Task.js +2 -0
- package/dist/domain/userStories/UserStory.d.ts +3 -0
- package/dist/domain/userStories/UserStory.d.ts.map +1 -0
- package/dist/domain/userStories/UserStory.js +2 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/prompts/PdrPrompts.d.ts +4 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -0
- package/dist/prompts/PdrPrompts.js +21 -0
- package/dist/prompts/PromptLoader.d.ts +3 -0
- package/dist/prompts/PromptLoader.d.ts.map +1 -0
- package/dist/prompts/PromptLoader.js +2 -0
- package/dist/prompts/SdsPrompts.d.ts +5 -0
- package/dist/prompts/SdsPrompts.d.ts.map +1 -0
- package/dist/prompts/SdsPrompts.js +44 -0
- package/dist/services/agents/AgentManagementService.d.ts +3 -0
- package/dist/services/agents/AgentManagementService.d.ts.map +1 -0
- package/dist/services/agents/AgentManagementService.js +2 -0
- package/dist/services/agents/GatewayAgentService.d.ts +92 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -0
- package/dist/services/agents/GatewayAgentService.js +870 -0
- package/dist/services/agents/RoutingApiClient.d.ts +23 -0
- package/dist/services/agents/RoutingApiClient.d.ts.map +1 -0
- package/dist/services/agents/RoutingApiClient.js +62 -0
- package/dist/services/agents/RoutingService.d.ts +50 -0
- package/dist/services/agents/RoutingService.d.ts.map +1 -0
- package/dist/services/agents/RoutingService.js +386 -0
- package/dist/services/agents/generated/RoutingApiClient.d.ts +21 -0
- package/dist/services/agents/generated/RoutingApiClient.d.ts.map +1 -0
- package/dist/services/agents/generated/RoutingApiClient.js +68 -0
- package/dist/services/backlog/BacklogService.d.ts +98 -0
- package/dist/services/backlog/BacklogService.d.ts.map +1 -0
- package/dist/services/backlog/BacklogService.js +453 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +88 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -0
- package/dist/services/backlog/TaskOrderingService.js +675 -0
- package/dist/services/docs/DocsService.d.ts +82 -0
- package/dist/services/docs/DocsService.d.ts.map +1 -0
- package/dist/services/docs/DocsService.js +1631 -0
- package/dist/services/estimate/EstimateService.d.ts +12 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -0
- package/dist/services/estimate/EstimateService.js +103 -0
- package/dist/services/estimate/VelocityService.d.ts +19 -0
- package/dist/services/estimate/VelocityService.d.ts.map +1 -0
- package/dist/services/estimate/VelocityService.js +237 -0
- package/dist/services/estimate/types.d.ts +30 -0
- package/dist/services/estimate/types.d.ts.map +1 -0
- package/dist/services/estimate/types.js +1 -0
- package/dist/services/execution/ExecutionService.d.ts +3 -0
- package/dist/services/execution/ExecutionService.d.ts.map +1 -0
- package/dist/services/execution/ExecutionService.js +2 -0
- package/dist/services/execution/QaFollowupService.d.ts +38 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -0
- package/dist/services/execution/QaFollowupService.js +236 -0
- package/dist/services/execution/QaProfileService.d.ts +22 -0
- package/dist/services/execution/QaProfileService.d.ts.map +1 -0
- package/dist/services/execution/QaProfileService.js +142 -0
- package/dist/services/execution/QaTasksService.d.ts +101 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -0
- package/dist/services/execution/QaTasksService.js +1117 -0
- package/dist/services/execution/TaskSelectionService.d.ts +50 -0
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -0
- package/dist/services/execution/TaskSelectionService.js +281 -0
- package/dist/services/execution/TaskStateService.d.ts +19 -0
- package/dist/services/execution/TaskStateService.d.ts.map +1 -0
- package/dist/services/execution/TaskStateService.js +59 -0
- package/dist/services/execution/WorkOnTasksService.d.ts +80 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -0
- package/dist/services/execution/WorkOnTasksService.js +1833 -0
- package/dist/services/jobs/JobInsightsService.d.ts +97 -0
- package/dist/services/jobs/JobInsightsService.d.ts.map +1 -0
- package/dist/services/jobs/JobInsightsService.js +263 -0
- package/dist/services/jobs/JobResumeService.d.ts +16 -0
- package/dist/services/jobs/JobResumeService.d.ts.map +1 -0
- package/dist/services/jobs/JobResumeService.js +113 -0
- package/dist/services/jobs/JobService.d.ts +149 -0
- package/dist/services/jobs/JobService.d.ts.map +1 -0
- package/dist/services/jobs/JobService.js +490 -0
- package/dist/services/jobs/JobsApiClient.d.ts +73 -0
- package/dist/services/jobs/JobsApiClient.d.ts.map +1 -0
- package/dist/services/jobs/JobsApiClient.js +67 -0
- package/dist/services/openapi/OpenApiService.d.ts +54 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -0
- package/dist/services/openapi/OpenApiService.js +503 -0
- package/dist/services/planning/CreateTasksService.d.ts +68 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -0
- package/dist/services/planning/CreateTasksService.js +989 -0
- package/dist/services/planning/KeyHelpers.d.ts +5 -0
- package/dist/services/planning/KeyHelpers.d.ts.map +1 -0
- package/dist/services/planning/KeyHelpers.js +62 -0
- package/dist/services/planning/PlanningService.d.ts +3 -0
- package/dist/services/planning/PlanningService.d.ts.map +1 -0
- package/dist/services/planning/PlanningService.js +2 -0
- package/dist/services/planning/RefineTasksService.d.ts +56 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -0
- package/dist/services/planning/RefineTasksService.js +1328 -0
- package/dist/services/review/CodeReviewService.d.ts +103 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -0
- package/dist/services/review/CodeReviewService.js +1187 -0
- package/dist/services/system/SystemUpdateService.d.ts +55 -0
- package/dist/services/system/SystemUpdateService.d.ts.map +1 -0
- package/dist/services/system/SystemUpdateService.js +136 -0
- package/dist/services/tasks/TaskApiResolver.d.ts +7 -0
- package/dist/services/tasks/TaskApiResolver.d.ts.map +1 -0
- package/dist/services/tasks/TaskApiResolver.js +41 -0
- package/dist/services/tasks/TaskDetailService.d.ts +106 -0
- package/dist/services/tasks/TaskDetailService.d.ts.map +1 -0
- package/dist/services/tasks/TaskDetailService.js +332 -0
- package/dist/services/telemetry/TelemetryService.d.ts +53 -0
- package/dist/services/telemetry/TelemetryService.d.ts.map +1 -0
- package/dist/services/telemetry/TelemetryService.js +434 -0
- package/dist/workspace/WorkspaceManager.d.ts +35 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -0
- package/dist/workspace/WorkspaceManager.js +201 -0
- package/package.json +45 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EstimateOptions, EstimateResult } from "./types.js";
|
|
2
|
+
import type { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
3
|
+
export declare class EstimateService {
|
|
4
|
+
private workspace;
|
|
5
|
+
private constructor();
|
|
6
|
+
static create(workspace: WorkspaceResolution): Promise<EstimateService>;
|
|
7
|
+
private computeDurations;
|
|
8
|
+
private computeEtas;
|
|
9
|
+
estimate(options: Omit<EstimateOptions, "workspace">): Promise<EstimateResult>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=EstimateService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EstimateService.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/EstimateService.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAmC,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAI/E,qBAAa,eAAe;IACN,OAAO,CAAC,SAAS;IAArC,OAAO;WAEM,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAI7E,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,WAAW;IAmBb,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAmD9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { BacklogService } from "../backlog/BacklogService.js";
|
|
2
|
+
import { VelocityService } from "./VelocityService.js";
|
|
3
|
+
const HOURS_IN_MS = 3600 * 1000;
|
|
4
|
+
export class EstimateService {
|
|
5
|
+
constructor(workspace) {
|
|
6
|
+
this.workspace = workspace;
|
|
7
|
+
}
|
|
8
|
+
static async create(workspace) {
|
|
9
|
+
return new EstimateService(workspace);
|
|
10
|
+
}
|
|
11
|
+
computeDurations(totals, velocity) {
|
|
12
|
+
const safeDivide = (sp, spPerHour) => {
|
|
13
|
+
if (!sp || sp <= 0)
|
|
14
|
+
return 0;
|
|
15
|
+
if (!spPerHour || spPerHour <= 0)
|
|
16
|
+
return null;
|
|
17
|
+
return sp / spPerHour;
|
|
18
|
+
};
|
|
19
|
+
const implementationHours = safeDivide(totals.implementation.story_points, velocity.implementationSpPerHour);
|
|
20
|
+
const reviewHours = safeDivide(totals.review.story_points, velocity.reviewSpPerHour);
|
|
21
|
+
const qaHours = safeDivide(totals.qa.story_points, velocity.qaSpPerHour);
|
|
22
|
+
const durationsList = [implementationHours, reviewHours, qaHours];
|
|
23
|
+
const hasNull = durationsList.some((value) => value === null);
|
|
24
|
+
const numeric = durationsList.filter((value) => value !== null);
|
|
25
|
+
const totalHours = hasNull || numeric.length === 0 ? null : Math.max(...numeric);
|
|
26
|
+
return {
|
|
27
|
+
implementationHours,
|
|
28
|
+
reviewHours,
|
|
29
|
+
qaHours,
|
|
30
|
+
totalHours,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
computeEtas(durations) {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const addHours = (hours) => {
|
|
36
|
+
if (hours === null || hours === undefined || hours < 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
return new Date(now + hours * HOURS_IN_MS).toISOString();
|
|
39
|
+
};
|
|
40
|
+
const readyToReviewEta = durations.implementationHours !== null ? addHours(durations.implementationHours ?? undefined) : undefined;
|
|
41
|
+
const readyToQaEta = durations.implementationHours !== null && durations.reviewHours !== null
|
|
42
|
+
? addHours(Math.max(durations.implementationHours ?? 0, durations.reviewHours ?? 0))
|
|
43
|
+
: undefined;
|
|
44
|
+
const completeEta = durations.totalHours !== null ? addHours(durations.totalHours ?? undefined) : undefined;
|
|
45
|
+
return {
|
|
46
|
+
readyToReviewEta,
|
|
47
|
+
readyToQaEta,
|
|
48
|
+
completeEta,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async estimate(options) {
|
|
52
|
+
const backlogService = await BacklogService.create(this.workspace);
|
|
53
|
+
let backlogTotals;
|
|
54
|
+
try {
|
|
55
|
+
const { summary } = await backlogService.getBacklog({
|
|
56
|
+
projectKey: options.projectKey,
|
|
57
|
+
epicKey: options.epicKey,
|
|
58
|
+
storyKey: options.storyKey,
|
|
59
|
+
assignee: options.assignee,
|
|
60
|
+
});
|
|
61
|
+
backlogTotals = summary.totals;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await backlogService.close();
|
|
65
|
+
}
|
|
66
|
+
const velocityService = await VelocityService.create(this.workspace);
|
|
67
|
+
let effectiveVelocity;
|
|
68
|
+
try {
|
|
69
|
+
effectiveVelocity = await velocityService.getEffectiveVelocity({
|
|
70
|
+
projectKey: options.projectKey,
|
|
71
|
+
epicKey: options.epicKey,
|
|
72
|
+
storyKey: options.storyKey,
|
|
73
|
+
assignee: options.assignee,
|
|
74
|
+
mode: options.mode,
|
|
75
|
+
windowTasks: options.windowTasks,
|
|
76
|
+
spPerHourAll: options.spPerHourAll,
|
|
77
|
+
spPerHourReview: options.spPerHourReview,
|
|
78
|
+
spPerHourQa: options.spPerHourQa,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await velocityService.close();
|
|
83
|
+
}
|
|
84
|
+
const durationsHours = this.computeDurations(backlogTotals, effectiveVelocity);
|
|
85
|
+
const etas = this.computeEtas(durationsHours);
|
|
86
|
+
return {
|
|
87
|
+
scope: {
|
|
88
|
+
workspaceId: this.workspace.workspaceId,
|
|
89
|
+
project: options.projectKey,
|
|
90
|
+
epic: options.epicKey,
|
|
91
|
+
story: options.storyKey,
|
|
92
|
+
assignee: options.assignee,
|
|
93
|
+
},
|
|
94
|
+
backlogTotals,
|
|
95
|
+
effectiveVelocity,
|
|
96
|
+
durationsHours,
|
|
97
|
+
etas,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async close() {
|
|
101
|
+
// No long-lived resources to release yet, placeholder for symmetry with other services.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
2
|
+
import type { EffectiveVelocity, VelocityOptions } from "./types.js";
|
|
3
|
+
export declare class VelocityService {
|
|
4
|
+
private workspace;
|
|
5
|
+
private db;
|
|
6
|
+
private connection;
|
|
7
|
+
private globalVelocity?;
|
|
8
|
+
private constructor();
|
|
9
|
+
static create(workspace: WorkspaceResolution): Promise<VelocityService>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
private static readGlobalVelocityConfig;
|
|
12
|
+
private resolveConfig;
|
|
13
|
+
private resolveScopeIds;
|
|
14
|
+
private buildTaskFilters;
|
|
15
|
+
private computeLaneVelocity;
|
|
16
|
+
private deriveDurationSeconds;
|
|
17
|
+
getEffectiveVelocity(options?: VelocityOptions): Promise<EffectiveVelocity>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=VelocityService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VelocityService.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/VelocityService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAkB,eAAe,EAAoB,MAAM,YAAY,CAAC;AAKvG,qBAAa,eAAe;IAExB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,cAAc,CAAC;IAJzB,OAAO;WAOM,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAOvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;mBAIP,wBAAwB;IAiB7C,OAAO,CAAC,aAAa;YAoCP,eAAe;IAgC7B,OAAO,CAAC,gBAAgB;YAuBV,mBAAmB;IAmEjC,OAAO,CAAC,qBAAqB;IAQvB,oBAAoB,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA2DtF"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Connection } from "@mcoda/db";
|
|
4
|
+
import { PathHelper } from "@mcoda/shared";
|
|
5
|
+
const DEFAULT_SP_PER_HOUR = 15;
|
|
6
|
+
const DEFAULT_ALPHA = 0.5;
|
|
7
|
+
export class VelocityService {
|
|
8
|
+
constructor(workspace, db, connection, globalVelocity) {
|
|
9
|
+
this.workspace = workspace;
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.connection = connection;
|
|
12
|
+
this.globalVelocity = globalVelocity;
|
|
13
|
+
}
|
|
14
|
+
static async create(workspace) {
|
|
15
|
+
const dbPath = PathHelper.getWorkspaceDbPath(workspace.workspaceRoot);
|
|
16
|
+
const connection = await Connection.open(dbPath);
|
|
17
|
+
const globalVelocity = await VelocityService.readGlobalVelocityConfig();
|
|
18
|
+
return new VelocityService(workspace, connection.db, connection, globalVelocity);
|
|
19
|
+
}
|
|
20
|
+
async close() {
|
|
21
|
+
await this.connection.close();
|
|
22
|
+
}
|
|
23
|
+
static async readGlobalVelocityConfig() {
|
|
24
|
+
const configPath = path.join(PathHelper.getGlobalMcodaDir(), "config.json");
|
|
25
|
+
try {
|
|
26
|
+
const raw = await fs.readFile(configPath, "utf8");
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if (!parsed.velocity)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
implementationSpPerHour: parsed.velocity.implementationSpPerHour ?? DEFAULT_SP_PER_HOUR,
|
|
32
|
+
reviewSpPerHour: parsed.velocity.reviewSpPerHour ?? DEFAULT_SP_PER_HOUR,
|
|
33
|
+
qaSpPerHour: parsed.velocity.qaSpPerHour ?? DEFAULT_SP_PER_HOUR,
|
|
34
|
+
alpha: parsed.velocity.alpha ?? DEFAULT_ALPHA,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
resolveConfig(options) {
|
|
42
|
+
const base = {
|
|
43
|
+
implementationSpPerHour: DEFAULT_SP_PER_HOUR,
|
|
44
|
+
reviewSpPerHour: DEFAULT_SP_PER_HOUR,
|
|
45
|
+
qaSpPerHour: DEFAULT_SP_PER_HOUR,
|
|
46
|
+
alpha: DEFAULT_ALPHA,
|
|
47
|
+
};
|
|
48
|
+
// Global config first
|
|
49
|
+
if (this.globalVelocity) {
|
|
50
|
+
base.implementationSpPerHour = this.globalVelocity.implementationSpPerHour ?? base.implementationSpPerHour;
|
|
51
|
+
base.reviewSpPerHour = this.globalVelocity.reviewSpPerHour ?? base.reviewSpPerHour;
|
|
52
|
+
base.qaSpPerHour = this.globalVelocity.qaSpPerHour ?? base.qaSpPerHour;
|
|
53
|
+
base.alpha = this.globalVelocity.alpha ?? base.alpha;
|
|
54
|
+
}
|
|
55
|
+
// Workspace config overrides global
|
|
56
|
+
if (this.workspace.config?.velocity) {
|
|
57
|
+
base.implementationSpPerHour =
|
|
58
|
+
this.workspace.config.velocity.implementationSpPerHour ?? base.implementationSpPerHour;
|
|
59
|
+
base.reviewSpPerHour = this.workspace.config.velocity.reviewSpPerHour ?? base.reviewSpPerHour;
|
|
60
|
+
base.qaSpPerHour = this.workspace.config.velocity.qaSpPerHour ?? base.qaSpPerHour;
|
|
61
|
+
base.alpha = this.workspace.config.velocity.alpha ?? base.alpha;
|
|
62
|
+
}
|
|
63
|
+
if (options.spPerHourAll !== undefined) {
|
|
64
|
+
base.implementationSpPerHour = options.spPerHourAll;
|
|
65
|
+
base.reviewSpPerHour = options.spPerHourAll;
|
|
66
|
+
base.qaSpPerHour = options.spPerHourAll;
|
|
67
|
+
}
|
|
68
|
+
if (options.spPerHourReview !== undefined) {
|
|
69
|
+
base.reviewSpPerHour = options.spPerHourReview;
|
|
70
|
+
}
|
|
71
|
+
if (options.spPerHourQa !== undefined) {
|
|
72
|
+
base.qaSpPerHour = options.spPerHourQa;
|
|
73
|
+
}
|
|
74
|
+
return base;
|
|
75
|
+
}
|
|
76
|
+
async resolveScopeIds(options) {
|
|
77
|
+
const scope = {};
|
|
78
|
+
if (options.projectKey) {
|
|
79
|
+
const row = await this.db.get(`SELECT id FROM projects WHERE key = ?`, options.projectKey);
|
|
80
|
+
if (!row) {
|
|
81
|
+
throw new Error(`Unknown project key: ${options.projectKey}`);
|
|
82
|
+
}
|
|
83
|
+
scope.projectId = row.id;
|
|
84
|
+
}
|
|
85
|
+
if (options.epicKey) {
|
|
86
|
+
const row = await this.db.get(`SELECT id FROM epics WHERE key = ? ${scope.projectId ? "AND project_id = ?" : ""}`, scope.projectId ? [options.epicKey, scope.projectId] : [options.epicKey]);
|
|
87
|
+
if (!row) {
|
|
88
|
+
throw new Error(`Unknown epic key: ${options.epicKey}`);
|
|
89
|
+
}
|
|
90
|
+
scope.epicId = row.id;
|
|
91
|
+
}
|
|
92
|
+
if (options.storyKey) {
|
|
93
|
+
const row = await this.db.get(`SELECT id FROM user_stories WHERE key = ? ${scope.epicId ? "AND epic_id = ?" : ""}`, scope.epicId ? [options.storyKey, scope.epicId] : [options.storyKey]);
|
|
94
|
+
if (!row) {
|
|
95
|
+
throw new Error(`Unknown user story key: ${options.storyKey}`);
|
|
96
|
+
}
|
|
97
|
+
scope.storyId = row.id;
|
|
98
|
+
}
|
|
99
|
+
return scope;
|
|
100
|
+
}
|
|
101
|
+
buildTaskFilters(scope, assignee) {
|
|
102
|
+
const clauses = [];
|
|
103
|
+
const params = [];
|
|
104
|
+
if (scope.projectId) {
|
|
105
|
+
clauses.push("t.project_id = ?");
|
|
106
|
+
params.push(scope.projectId);
|
|
107
|
+
}
|
|
108
|
+
if (scope.epicId) {
|
|
109
|
+
clauses.push("t.epic_id = ?");
|
|
110
|
+
params.push(scope.epicId);
|
|
111
|
+
}
|
|
112
|
+
if (scope.storyId) {
|
|
113
|
+
clauses.push("t.user_story_id = ?");
|
|
114
|
+
params.push(scope.storyId);
|
|
115
|
+
}
|
|
116
|
+
if (assignee) {
|
|
117
|
+
clauses.push("LOWER(t.assignee_human) = LOWER(?)");
|
|
118
|
+
params.push(assignee);
|
|
119
|
+
}
|
|
120
|
+
if (clauses.length === 0)
|
|
121
|
+
return { clause: "", params: [] };
|
|
122
|
+
return { clause: `AND ${clauses.join(" AND ")}`, params };
|
|
123
|
+
}
|
|
124
|
+
async computeLaneVelocity(commandName, scope, assignee, windowTasks) {
|
|
125
|
+
const runs = await this.db.all(`
|
|
126
|
+
SELECT id, started_at, completed_at, duration_seconds
|
|
127
|
+
FROM command_runs
|
|
128
|
+
WHERE command_name = ?
|
|
129
|
+
AND status IN ('success','succeeded')
|
|
130
|
+
ORDER BY COALESCE(completed_at, started_at) DESC
|
|
131
|
+
`, commandName);
|
|
132
|
+
let totalSp = 0;
|
|
133
|
+
let totalDurationSeconds = 0;
|
|
134
|
+
let samples = 0;
|
|
135
|
+
const filters = this.buildTaskFilters(scope, assignee);
|
|
136
|
+
for (const run of runs) {
|
|
137
|
+
const aggregation = await this.db.get(`
|
|
138
|
+
SELECT
|
|
139
|
+
SUM(COALESCE(t.story_points, 0)) as sp,
|
|
140
|
+
COUNT(*) as tasks
|
|
141
|
+
FROM task_runs tr
|
|
142
|
+
INNER JOIN tasks t ON t.id = tr.task_id
|
|
143
|
+
WHERE tr.command_run_id = ?
|
|
144
|
+
${filters.clause}
|
|
145
|
+
`, run.id, ...filters.params);
|
|
146
|
+
const taskCount = aggregation?.tasks ?? 0;
|
|
147
|
+
const sp = aggregation?.sp ?? 0;
|
|
148
|
+
if (taskCount === 0) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const durationSeconds = typeof run.duration_seconds === "number" && Number.isFinite(run.duration_seconds)
|
|
152
|
+
? run.duration_seconds
|
|
153
|
+
: this.deriveDurationSeconds(run.started_at, run.completed_at);
|
|
154
|
+
if (!durationSeconds || durationSeconds <= 0) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
totalSp += sp;
|
|
158
|
+
totalDurationSeconds += durationSeconds;
|
|
159
|
+
samples += taskCount;
|
|
160
|
+
if (samples >= windowTasks)
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (samples === 0 || totalDurationSeconds <= 0 || totalSp <= 0) {
|
|
164
|
+
return { samples: 0 };
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
spPerHour: totalSp / (totalDurationSeconds / 3600),
|
|
168
|
+
samples,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
deriveDurationSeconds(started, completed) {
|
|
172
|
+
if (!started || !completed)
|
|
173
|
+
return undefined;
|
|
174
|
+
const startMs = Date.parse(started);
|
|
175
|
+
const endMs = Date.parse(completed);
|
|
176
|
+
if (Number.isNaN(startMs) || Number.isNaN(endMs) || endMs <= startMs)
|
|
177
|
+
return undefined;
|
|
178
|
+
return (endMs - startMs) / 1000;
|
|
179
|
+
}
|
|
180
|
+
async getEffectiveVelocity(options = {}) {
|
|
181
|
+
const mode = options.mode ?? "config";
|
|
182
|
+
const windowTasks = options.windowTasks ?? 10;
|
|
183
|
+
const config = this.resolveConfig(options);
|
|
184
|
+
if (mode === "config") {
|
|
185
|
+
return {
|
|
186
|
+
implementationSpPerHour: config.implementationSpPerHour,
|
|
187
|
+
reviewSpPerHour: config.reviewSpPerHour,
|
|
188
|
+
qaSpPerHour: config.qaSpPerHour,
|
|
189
|
+
source: "config",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const scope = await this.resolveScopeIds(options);
|
|
193
|
+
const implementation = await this.computeLaneVelocity("work-on-tasks", scope, options.assignee, windowTasks);
|
|
194
|
+
const review = await this.computeLaneVelocity("code-review", scope, options.assignee, windowTasks);
|
|
195
|
+
const qa = await this.computeLaneVelocity("qa-tasks", scope, options.assignee, windowTasks);
|
|
196
|
+
const alpha = config.alpha ?? DEFAULT_ALPHA;
|
|
197
|
+
const resolveLane = (empirical) => {
|
|
198
|
+
if (empirical === undefined || empirical <= 0)
|
|
199
|
+
return config.implementationSpPerHour;
|
|
200
|
+
if (mode === "empirical")
|
|
201
|
+
return empirical;
|
|
202
|
+
return alpha * empirical + (1 - alpha) * config.implementationSpPerHour;
|
|
203
|
+
};
|
|
204
|
+
const resolveReviewLane = (empirical) => {
|
|
205
|
+
if (empirical === undefined || empirical <= 0)
|
|
206
|
+
return config.reviewSpPerHour;
|
|
207
|
+
if (mode === "empirical")
|
|
208
|
+
return empirical;
|
|
209
|
+
return alpha * empirical + (1 - alpha) * config.reviewSpPerHour;
|
|
210
|
+
};
|
|
211
|
+
const resolveQaLane = (empirical) => {
|
|
212
|
+
if (empirical === undefined || empirical <= 0)
|
|
213
|
+
return config.qaSpPerHour;
|
|
214
|
+
if (mode === "empirical")
|
|
215
|
+
return empirical;
|
|
216
|
+
return alpha * empirical + (1 - alpha) * config.qaSpPerHour;
|
|
217
|
+
};
|
|
218
|
+
const implementationSpPerHour = resolveLane(implementation.spPerHour);
|
|
219
|
+
const reviewSpPerHour = resolveReviewLane(review.spPerHour);
|
|
220
|
+
const qaSpPerHour = resolveQaLane(qa.spPerHour);
|
|
221
|
+
const usedEmpirical = (implementation.spPerHour && implementation.spPerHour > 0) ||
|
|
222
|
+
(review.spPerHour && review.spPerHour > 0) ||
|
|
223
|
+
(qa.spPerHour && qa.spPerHour > 0);
|
|
224
|
+
return {
|
|
225
|
+
implementationSpPerHour,
|
|
226
|
+
reviewSpPerHour,
|
|
227
|
+
qaSpPerHour,
|
|
228
|
+
source: usedEmpirical ? mode : "config",
|
|
229
|
+
windowTasks,
|
|
230
|
+
samples: {
|
|
231
|
+
implementation: implementation.samples,
|
|
232
|
+
review: review.samples,
|
|
233
|
+
qa: qa.samples,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { EffectiveVelocity, EstimateResult, EstimateDurations, EstimateEtas, VelocitySource } from "@mcoda/shared";
|
|
2
|
+
import type { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
3
|
+
export interface VelocityConfig {
|
|
4
|
+
implementationSpPerHour: number;
|
|
5
|
+
reviewSpPerHour: number;
|
|
6
|
+
qaSpPerHour: number;
|
|
7
|
+
alpha?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface VelocityOptions {
|
|
10
|
+
projectKey?: string;
|
|
11
|
+
epicKey?: string;
|
|
12
|
+
storyKey?: string;
|
|
13
|
+
assignee?: string;
|
|
14
|
+
mode?: VelocitySource;
|
|
15
|
+
windowTasks?: 10 | 20 | 50;
|
|
16
|
+
spPerHourAll?: number;
|
|
17
|
+
spPerHourReview?: number;
|
|
18
|
+
spPerHourQa?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface VelocityScopeIds {
|
|
21
|
+
projectId?: string;
|
|
22
|
+
epicId?: string;
|
|
23
|
+
storyId?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface EstimateOptions extends VelocityOptions {
|
|
26
|
+
workspace: WorkspaceResolution;
|
|
27
|
+
}
|
|
28
|
+
export type { EstimateDurations, EstimateEtas };
|
|
29
|
+
export type { EffectiveVelocity, EstimateResult, VelocitySource };
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE/E,MAAM,WAAW,cAAc;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,SAAS,EAAE,mBAAmB,CAAC;CAChC;AAED,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExecutionService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/ExecutionService.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAgB;CAAG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TaskCommentInsert, TaskDependencyInsert, TaskInsert, TaskRow, WorkspaceRepository } from '@mcoda/db';
|
|
2
|
+
export interface FollowupSuggestion {
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
priority?: number;
|
|
7
|
+
storyPoints?: number;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
epicKeyHint?: string;
|
|
10
|
+
storyKeyHint?: string;
|
|
11
|
+
relatedTaskKey?: string;
|
|
12
|
+
components?: string[];
|
|
13
|
+
docLinks?: string[];
|
|
14
|
+
testName?: string;
|
|
15
|
+
evidenceUrl?: string;
|
|
16
|
+
artifacts?: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare class QaFollowupService {
|
|
19
|
+
private workspaceRepo;
|
|
20
|
+
private workspaceRoot;
|
|
21
|
+
constructor(workspaceRepo: WorkspaceRepository, workspaceRoot: string);
|
|
22
|
+
private get cachePath();
|
|
23
|
+
private readCache;
|
|
24
|
+
private writeCache;
|
|
25
|
+
private ensureBugContainer;
|
|
26
|
+
createFollowupTask(sourceTask: TaskRow & {
|
|
27
|
+
storyKey?: string;
|
|
28
|
+
epicKey?: string;
|
|
29
|
+
}, suggestion: FollowupSuggestion): Promise<{
|
|
30
|
+
task: TaskInsert & {
|
|
31
|
+
id: string;
|
|
32
|
+
};
|
|
33
|
+
dependency?: TaskDependencyInsert;
|
|
34
|
+
comment?: TaskCommentInsert;
|
|
35
|
+
}>;
|
|
36
|
+
private resolveTargetContainer;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=QaFollowupService.d.ts.map
|
|
@@ -0,0 +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;CACtB;AAgCD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,aAAa;IAAuB,OAAO,CAAC,aAAa;gBAAzD,aAAa,EAAE,mBAAmB,EAAU,aAAa,EAAE,MAAM;IAErF,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;YA6EnG,sBAAsB;CA+ErC"}
|