@mcoda/core 0.1.8 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/README.md +2 -2
- package/dist/api/AgentsApi.d.ts +9 -1
- package/dist/api/AgentsApi.d.ts.map +1 -1
- package/dist/api/AgentsApi.js +201 -6
- package/dist/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +6 -0
- package/dist/api/TasksApi.d.ts.map +1 -1
- package/dist/api/TasksApi.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +9 -1
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +9 -0
- package/dist/services/agents/AgentRatingFormula.d.ts +27 -0
- package/dist/services/agents/AgentRatingFormula.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingFormula.js +45 -0
- package/dist/services/agents/AgentRatingService.d.ts +60 -0
- package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingService.js +363 -0
- package/dist/services/agents/GatewayAgentService.d.ts +11 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
- package/dist/services/agents/GatewayAgentService.js +525 -84
- package/dist/services/agents/GatewayHandoff.d.ts +11 -0
- package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
- package/dist/services/agents/GatewayHandoff.js +141 -0
- package/dist/services/agents/RoutingService.d.ts +1 -0
- package/dist/services/agents/RoutingService.d.ts.map +1 -1
- package/dist/services/agents/RoutingService.js +4 -4
- package/dist/services/backlog/BacklogService.d.ts +23 -0
- package/dist/services/backlog/BacklogService.d.ts.map +1 -1
- package/dist/services/backlog/BacklogService.js +62 -7
- package/dist/services/backlog/TaskOrderingHeuristics.d.ts +12 -0
- package/dist/services/backlog/TaskOrderingHeuristics.d.ts.map +1 -0
- package/dist/services/backlog/TaskOrderingHeuristics.js +56 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +17 -4
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +538 -79
- package/dist/services/docs/DocInventory.d.ts +11 -0
- package/dist/services/docs/DocInventory.d.ts.map +1 -0
- package/dist/services/docs/DocInventory.js +230 -0
- package/dist/services/docs/DocgenRunContext.d.ts +59 -0
- package/dist/services/docs/DocgenRunContext.d.ts.map +1 -0
- package/dist/services/docs/DocgenRunContext.js +4 -0
- package/dist/services/docs/DocsService.d.ts +70 -3
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +1930 -89
- package/dist/services/docs/alignment/DocAlignmentGraph.d.ts +23 -0
- package/dist/services/docs/alignment/DocAlignmentGraph.d.ts.map +1 -0
- package/dist/services/docs/alignment/DocAlignmentGraph.js +78 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts +19 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts.map +1 -0
- package/dist/services/docs/alignment/DocAlignmentPatcher.js +222 -0
- package/dist/services/docs/patch/DocPatchEngine.d.ts +57 -0
- package/dist/services/docs/patch/DocPatchEngine.d.ts.map +1 -0
- package/dist/services/docs/patch/DocPatchEngine.js +331 -0
- package/dist/services/docs/review/Glossary.d.ts +16 -0
- package/dist/services/docs/review/Glossary.d.ts.map +1 -0
- package/dist/services/docs/review/Glossary.js +47 -0
- package/dist/services/docs/review/ReviewReportRenderer.d.ts +3 -0
- package/dist/services/docs/review/ReviewReportRenderer.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewReportRenderer.js +133 -0
- package/dist/services/docs/review/ReviewReportSchema.d.ts +39 -0
- package/dist/services/docs/review/ReviewReportSchema.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewReportSchema.js +47 -0
- package/dist/services/docs/review/ReviewTypes.d.ts +76 -0
- package/dist/services/docs/review/ReviewTypes.d.ts.map +1 -0
- package/dist/services/docs/review/ReviewTypes.js +94 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts +7 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/AdminOpenApiSpecGate.js +93 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts +7 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/ApiPathConsistencyGate.js +308 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts +8 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/BuildReadyCompletenessGate.js +278 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts +8 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/DeploymentBlueprintGate.js +487 -0
- package/dist/services/docs/review/gates/NoMaybesGate.d.ts +8 -0
- package/dist/services/docs/review/gates/NoMaybesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/NoMaybesGate.js +145 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenApiCoverageGate.js +266 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.js +59 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/OpenQuestionsGate.js +200 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrInterfacesGate.js +159 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts +8 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrOpenQuestionsGate.js +129 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrOwnershipGate.js +169 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts +10 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PlaceholderArtifactGate.js +261 -0
- package/dist/services/docs/review/gates/RfpConsentGate.d.ts +6 -0
- package/dist/services/docs/review/gates/RfpConsentGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/RfpConsentGate.js +127 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts +7 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/RfpDefinitionGate.js +173 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsAdaptersGate.js +196 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsDecisionsGate.js +89 -0
- package/dist/services/docs/review/gates/SdsOpsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsOpsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsOpsGate.js +162 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.js +166 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SqlRequiredTablesGate.js +273 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SqlSyntaxGate.js +203 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts +9 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/TerminologyNormalizationGate.js +217 -0
- package/dist/services/docs/review/glossary.json +47 -0
- package/dist/services/estimate/EstimateService.d.ts +2 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -1
- package/dist/services/estimate/EstimateService.js +66 -18
- package/dist/services/estimate/VelocityService.d.ts +4 -0
- package/dist/services/estimate/VelocityService.d.ts.map +1 -1
- package/dist/services/estimate/VelocityService.js +179 -36
- package/dist/services/estimate/types.d.ts +1 -0
- package/dist/services/estimate/types.d.ts.map +1 -1
- package/dist/services/execution/GatewayTrioService.d.ts +200 -0
- package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
- package/dist/services/execution/GatewayTrioService.js +2492 -0
- package/dist/services/execution/QaApiRunner.d.ts +30 -0
- package/dist/services/execution/QaApiRunner.d.ts.map +1 -0
- package/dist/services/execution/QaApiRunner.js +881 -0
- package/dist/services/execution/QaFollowupService.d.ts +2 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
- package/dist/services/execution/QaFollowupService.js +9 -2
- package/dist/services/execution/QaPlanValidator.d.ts +10 -0
- package/dist/services/execution/QaPlanValidator.d.ts.map +1 -0
- package/dist/services/execution/QaPlanValidator.js +128 -0
- package/dist/services/execution/QaProfileService.d.ts +27 -1
- package/dist/services/execution/QaProfileService.d.ts.map +1 -1
- package/dist/services/execution/QaProfileService.js +354 -7
- package/dist/services/execution/QaTasksService.d.ts +59 -1
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +3347 -318
- package/dist/services/execution/QaTestCommandBuilder.d.ts +51 -0
- package/dist/services/execution/QaTestCommandBuilder.d.ts.map +1 -0
- package/dist/services/execution/QaTestCommandBuilder.js +495 -0
- package/dist/services/execution/TaskSelectionService.d.ts +4 -2
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +144 -28
- package/dist/services/execution/TaskStateService.d.ts +19 -6
- package/dist/services/execution/TaskStateService.d.ts.map +1 -1
- package/dist/services/execution/TaskStateService.js +128 -13
- package/dist/services/execution/WorkOnTasksService.d.ts +32 -1
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +4667 -722
- package/dist/services/jobs/JobInsightsService.d.ts +4 -0
- package/dist/services/jobs/JobInsightsService.d.ts.map +1 -1
- package/dist/services/jobs/JobInsightsService.js +51 -5
- package/dist/services/jobs/JobResumeService.d.ts.map +1 -1
- package/dist/services/jobs/JobResumeService.js +23 -10
- package/dist/services/jobs/JobService.d.ts +56 -4
- package/dist/services/jobs/JobService.d.ts.map +1 -1
- package/dist/services/jobs/JobService.js +232 -1
- package/dist/services/openapi/OpenApiService.d.ts +51 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +953 -106
- package/dist/services/planning/CreateTasksService.d.ts +21 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +569 -31
- package/dist/services/planning/RefineTasksService.d.ts +9 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +409 -59
- package/dist/services/review/CodeReviewService.d.ts +18 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +1309 -167
- package/dist/services/review/ReviewNormalizer.d.ts +9 -0
- package/dist/services/review/ReviewNormalizer.d.ts.map +1 -0
- package/dist/services/review/ReviewNormalizer.js +147 -0
- package/dist/services/shared/AuthErrors.d.ts +3 -0
- package/dist/services/shared/AuthErrors.d.ts.map +1 -0
- package/dist/services/shared/AuthErrors.js +17 -0
- package/dist/services/shared/DocdexGuidance.d.ts +7 -0
- package/dist/services/shared/DocdexGuidance.d.ts.map +1 -0
- package/dist/services/shared/DocdexGuidance.js +12 -0
- package/dist/services/shared/ProjectGuidance.d.ts +17 -0
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
- package/dist/services/shared/ProjectGuidance.js +78 -0
- package/dist/services/system/ToolDenylist.d.ts +13 -0
- package/dist/services/system/ToolDenylist.d.ts.map +1 -0
- package/dist/services/system/ToolDenylist.js +85 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts +20 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts.map +1 -0
- package/dist/services/tasks/TaskCommentFormatter.js +54 -0
- package/dist/services/telemetry/TelemetryService.d.ts.map +1 -1
- package/dist/services/telemetry/TelemetryService.js +39 -7
- package/dist/workspace/WorkspaceManager.d.ts +26 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
- package/dist/workspace/WorkspaceManager.js +206 -32
- package/package.json +6 -5
|
@@ -2,58 +2,162 @@ import path from "node:path";
|
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import { AgentService } from "@mcoda/agents";
|
|
4
4
|
import { DocdexClient } from "@mcoda/integrations";
|
|
5
|
+
import { READY_TO_CODE_REVIEW } from "@mcoda/shared";
|
|
5
6
|
import { GlobalRepository, WorkspaceRepository } from "@mcoda/db";
|
|
7
|
+
import { getCommandRequiredCapabilities, } from "@mcoda/shared";
|
|
6
8
|
import { JobService } from "../jobs/JobService.js";
|
|
7
9
|
import { RoutingService } from "../agents/RoutingService.js";
|
|
10
|
+
import { AgentRatingService } from "../agents/AgentRatingService.js";
|
|
11
|
+
import { classifyTask } from "../backlog/TaskOrderingHeuristics.js";
|
|
12
|
+
import { TaskOrderingService } from "../backlog/TaskOrderingService.js";
|
|
8
13
|
import { createTaskKeyGenerator } from "./KeyHelpers.js";
|
|
9
14
|
const DEFAULT_STRATEGY = "auto";
|
|
10
|
-
const FORBIDDEN_TARGET_STATUSES = new Set([
|
|
15
|
+
const FORBIDDEN_TARGET_STATUSES = new Set([READY_TO_CODE_REVIEW, "ready_to_qa", "completed"]);
|
|
16
|
+
const normalizeCreateStatus = (status) => {
|
|
17
|
+
if (!status)
|
|
18
|
+
return "not_started";
|
|
19
|
+
return status.toLowerCase();
|
|
20
|
+
};
|
|
11
21
|
const DEFAULT_MAX_TASKS = 250;
|
|
12
22
|
const MAX_AGENT_OUTPUT_CHARS = 10000000;
|
|
13
23
|
const estimateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
14
24
|
const extractJson = (raw) => {
|
|
25
|
+
const fencedMatches = [...raw.matchAll(/```json([\s\S]*?)```/g)].map((match) => match[1]);
|
|
26
|
+
const stripped = raw.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
27
|
+
const candidates = [...fencedMatches, stripped, raw].filter((candidate) => candidate.trim().length > 0);
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
const parsed = tryParseJson(candidate);
|
|
30
|
+
if (parsed !== undefined)
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
const tryParseJson = (value) => {
|
|
15
36
|
try {
|
|
16
|
-
|
|
17
|
-
const candidate = fenced ? fenced[1] : raw;
|
|
18
|
-
const start = candidate.indexOf("{");
|
|
19
|
-
const end = candidate.lastIndexOf("}");
|
|
20
|
-
if (start === -1 || end === -1 || end <= start)
|
|
21
|
-
return JSON.parse(raw);
|
|
22
|
-
return JSON.parse(candidate.slice(start, end + 1));
|
|
37
|
+
return JSON.parse(value);
|
|
23
38
|
}
|
|
24
39
|
catch {
|
|
40
|
+
// continue
|
|
41
|
+
}
|
|
42
|
+
const objects = extractJsonObjects(value).reverse();
|
|
43
|
+
for (const obj of objects) {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(obj);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// continue
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
const extractJsonObjects = (value) => {
|
|
54
|
+
const results = [];
|
|
55
|
+
let depth = 0;
|
|
56
|
+
let start = -1;
|
|
57
|
+
let inString = false;
|
|
58
|
+
let escaped = false;
|
|
59
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
60
|
+
const ch = value[i];
|
|
61
|
+
if (inString) {
|
|
62
|
+
if (escaped) {
|
|
63
|
+
escaped = false;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ch === "\\") {
|
|
67
|
+
escaped = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === "\"")
|
|
71
|
+
inString = false;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (ch === "\"") {
|
|
75
|
+
inString = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ch === "{") {
|
|
79
|
+
if (depth === 0)
|
|
80
|
+
start = i;
|
|
81
|
+
depth += 1;
|
|
82
|
+
}
|
|
83
|
+
else if (ch === "}") {
|
|
84
|
+
if (depth === 0)
|
|
85
|
+
continue;
|
|
86
|
+
depth -= 1;
|
|
87
|
+
if (depth === 0 && start >= 0) {
|
|
88
|
+
results.push(value.slice(start, i + 1));
|
|
89
|
+
start = -1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
};
|
|
95
|
+
const normalizePlanJson = (parsed) => {
|
|
96
|
+
if (!parsed)
|
|
25
97
|
return undefined;
|
|
98
|
+
if (Array.isArray(parsed)) {
|
|
99
|
+
return { operations: parsed };
|
|
26
100
|
}
|
|
101
|
+
if (Array.isArray(parsed.operations))
|
|
102
|
+
return parsed;
|
|
103
|
+
if (parsed.op) {
|
|
104
|
+
return { operations: [parsed] };
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
27
107
|
};
|
|
28
108
|
const normalizeOperation = (op) => {
|
|
29
109
|
if (!op || typeof op !== "object")
|
|
30
110
|
return op;
|
|
31
|
-
|
|
32
|
-
return op;
|
|
111
|
+
const opType = op.op;
|
|
33
112
|
const taskKey = op.taskKey ?? op.key ?? op.task ?? op.targetTaskKey ?? null;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
113
|
+
if (opType === "update_task") {
|
|
114
|
+
const updates = { ...op.updates };
|
|
115
|
+
const inlineFields = [
|
|
116
|
+
"title",
|
|
117
|
+
"description",
|
|
118
|
+
"acceptanceCriteria",
|
|
119
|
+
"type",
|
|
120
|
+
"status",
|
|
121
|
+
"storyPoints",
|
|
122
|
+
"priority",
|
|
123
|
+
"dependsOn",
|
|
124
|
+
"metadata",
|
|
125
|
+
];
|
|
126
|
+
for (const field of inlineFields) {
|
|
127
|
+
if (op[field] !== undefined && updates[field] === undefined) {
|
|
128
|
+
updates[field] = op[field];
|
|
129
|
+
}
|
|
39
130
|
}
|
|
131
|
+
return {
|
|
132
|
+
...op,
|
|
133
|
+
taskKey,
|
|
134
|
+
updates,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (opType === "split_task") {
|
|
138
|
+
const children = Array.isArray(op.children)
|
|
139
|
+
? op.children
|
|
140
|
+
: Array.isArray(op.subtasks)
|
|
141
|
+
? op.subtasks
|
|
142
|
+
: Array.isArray(op.newTasks)
|
|
143
|
+
? op.newTasks
|
|
144
|
+
: Array.isArray(op.tasks)
|
|
145
|
+
? op.tasks
|
|
146
|
+
: undefined;
|
|
147
|
+
return {
|
|
148
|
+
...op,
|
|
149
|
+
taskKey,
|
|
150
|
+
children,
|
|
151
|
+
};
|
|
40
152
|
}
|
|
41
153
|
return {
|
|
42
154
|
...op,
|
|
43
155
|
taskKey,
|
|
44
|
-
updates,
|
|
45
156
|
};
|
|
46
157
|
};
|
|
47
158
|
const safeParsePlan = (content) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (parsed && Array.isArray(parsed.operations))
|
|
51
|
-
return parsed;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
/* ignore */
|
|
55
|
-
}
|
|
56
|
-
return undefined;
|
|
159
|
+
const parsed = extractJson(content);
|
|
160
|
+
return normalizePlanJson(parsed);
|
|
57
161
|
};
|
|
58
162
|
const formatTaskSummary = (task) => {
|
|
59
163
|
return [
|
|
@@ -73,14 +177,17 @@ export class RefineTasksService {
|
|
|
73
177
|
this.repo = deps.repo;
|
|
74
178
|
this.workspaceRepo = deps.workspaceRepo;
|
|
75
179
|
this.routingService = deps.routingService;
|
|
180
|
+
this.ratingService = deps.ratingService;
|
|
76
181
|
}
|
|
77
182
|
static async create(workspace) {
|
|
78
183
|
const repo = await GlobalRepository.create();
|
|
79
184
|
const agentService = new AgentService(repo);
|
|
80
185
|
const routingService = await RoutingService.create();
|
|
186
|
+
const docdexRepoId = workspace.config?.docdexRepoId ?? process.env.MCODA_DOCDEX_REPO_ID ?? process.env.DOCDEX_REPO_ID;
|
|
81
187
|
const docdex = new DocdexClient({
|
|
82
188
|
workspaceRoot: workspace.workspaceRoot,
|
|
83
189
|
baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
|
|
190
|
+
repoId: docdexRepoId,
|
|
84
191
|
});
|
|
85
192
|
const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
|
|
86
193
|
const jobService = new JobService(workspace, workspaceRepo);
|
|
@@ -111,13 +218,93 @@ export class RefineTasksService {
|
|
|
111
218
|
await tryClose(this.routingService);
|
|
112
219
|
await tryClose(this.docdex);
|
|
113
220
|
}
|
|
221
|
+
async seedPriorities(projectKey) {
|
|
222
|
+
const ordering = await TaskOrderingService.create(this.workspace, { recordTelemetry: false });
|
|
223
|
+
try {
|
|
224
|
+
await ordering.orderTasks({
|
|
225
|
+
projectKey,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
await ordering.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
114
232
|
async resolveAgent(agentName) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
233
|
+
try {
|
|
234
|
+
const resolved = await this.routingService.resolveAgentForCommand({
|
|
235
|
+
workspace: this.workspace,
|
|
236
|
+
commandName: "refine-tasks",
|
|
237
|
+
overrideAgentSlug: agentName,
|
|
238
|
+
});
|
|
239
|
+
return resolved.agent;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
const message = error.message ?? "";
|
|
243
|
+
if (!/No routing defaults/i.test(message)) {
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
const requiredCaps = getCommandRequiredCapabilities("refine-tasks");
|
|
247
|
+
const fallback = await this.selectFallbackAgent(requiredCaps);
|
|
248
|
+
if (fallback)
|
|
249
|
+
return fallback;
|
|
250
|
+
throw new Error(`No routing defaults found for command refine-tasks. ` +
|
|
251
|
+
`Set a default agent (mcoda agent set-default <NAME> --workspace <PATH>) ` +
|
|
252
|
+
`or pass --agent <NAME> with ${requiredCaps.length ? `capabilities: ${requiredCaps.join(", ")}` : "required capabilities"}.`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async selectFallbackAgent(requiredCaps) {
|
|
256
|
+
const agents = await this.repo.listAgents();
|
|
257
|
+
if (!agents.length)
|
|
258
|
+
return undefined;
|
|
259
|
+
let healthRows = [];
|
|
260
|
+
try {
|
|
261
|
+
healthRows = await this.repo.listAgentHealthSummary();
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
healthRows = [];
|
|
265
|
+
}
|
|
266
|
+
const healthById = new Map(healthRows.map((row) => [row.agentId, row]));
|
|
267
|
+
const candidates = [];
|
|
268
|
+
for (const agent of agents) {
|
|
269
|
+
const health = healthById.get(agent.id);
|
|
270
|
+
if (health?.status === "unreachable")
|
|
271
|
+
continue;
|
|
272
|
+
const caps = await this.repo.getAgentCapabilities(agent.id);
|
|
273
|
+
const hasCaps = requiredCaps.every((cap) => caps.includes(cap));
|
|
274
|
+
candidates.push({
|
|
275
|
+
agent,
|
|
276
|
+
rating: Number(agent.rating ?? 0),
|
|
277
|
+
reasoning: Number(agent.reasoningRating ?? 0),
|
|
278
|
+
cost: Number(agent.costPerMillion ?? 0),
|
|
279
|
+
hasCaps,
|
|
280
|
+
slug: agent.slug ?? agent.id,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (!candidates.length)
|
|
284
|
+
return undefined;
|
|
285
|
+
const eligible = candidates.filter((c) => c.hasCaps);
|
|
286
|
+
const pool = eligible.length ? eligible : candidates;
|
|
287
|
+
pool.sort((a, b) => {
|
|
288
|
+
if (b.rating !== a.rating)
|
|
289
|
+
return b.rating - a.rating;
|
|
290
|
+
if (b.reasoning !== a.reasoning)
|
|
291
|
+
return b.reasoning - a.reasoning;
|
|
292
|
+
if (a.cost !== b.cost)
|
|
293
|
+
return a.cost - b.cost;
|
|
294
|
+
return a.slug.localeCompare(b.slug);
|
|
119
295
|
});
|
|
120
|
-
return
|
|
296
|
+
return pool[0]?.agent;
|
|
297
|
+
}
|
|
298
|
+
ensureRatingService() {
|
|
299
|
+
if (!this.ratingService) {
|
|
300
|
+
this.ratingService = new AgentRatingService(this.workspace, {
|
|
301
|
+
workspaceRepo: this.workspaceRepo,
|
|
302
|
+
globalRepo: this.repo,
|
|
303
|
+
agentService: this.agentService,
|
|
304
|
+
routingService: this.routingService,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return this.ratingService;
|
|
121
308
|
}
|
|
122
309
|
async selectTasks(projectKey, filters) {
|
|
123
310
|
const db = this.workspaceRepo.getDb();
|
|
@@ -361,6 +548,7 @@ export class RefineTasksService {
|
|
|
361
548
|
const updates = seed?.updates ?? seed?.fields ?? {};
|
|
362
549
|
const epic = await ensureEpic();
|
|
363
550
|
const story = await ensureStory(epic.id);
|
|
551
|
+
const status = normalizeCreateStatus(updates.status);
|
|
364
552
|
const [task] = await this.workspaceRepo.insertTasks([
|
|
365
553
|
{
|
|
366
554
|
projectId,
|
|
@@ -370,7 +558,7 @@ export class RefineTasksService {
|
|
|
370
558
|
title: updates.title ?? `Task ${taskKey}`,
|
|
371
559
|
description: updates.description ?? "",
|
|
372
560
|
type: updates.type ?? "feature",
|
|
373
|
-
status
|
|
561
|
+
status,
|
|
374
562
|
storyPoints: updates.storyPoints ?? null,
|
|
375
563
|
priority: updates.priority ?? null,
|
|
376
564
|
metadata: updates.metadata ?? undefined,
|
|
@@ -391,7 +579,7 @@ export class RefineTasksService {
|
|
|
391
579
|
const taskList = group.tasks.map((t) => formatTaskSummary(t)).join("\n");
|
|
392
580
|
const constraints = [
|
|
393
581
|
"- Immutable: project_id, epic_id, user_story_id, task keys.",
|
|
394
|
-
"- Allowed edits: title, description, acceptanceCriteria, metadata/labels, type, priority, storyPoints, status (but NOT
|
|
582
|
+
"- Allowed edits: title, description, acceptanceCriteria, metadata/labels, type, priority, storyPoints, status (but NOT ready_to_code_review/qa/completed).",
|
|
395
583
|
"- Splits: children stay under same story; keep parent unless keepParent=false; child dependsOn must reference existing tasks or siblings.",
|
|
396
584
|
"- Merges: target and sources must be in same story; prefer cancelling redundant sources (status=cancelled) and preserve useful details in target updates.",
|
|
397
585
|
"- Dependencies: maintain DAG; do not introduce cycles or cross-story edges.",
|
|
@@ -411,6 +599,8 @@ export class RefineTasksService {
|
|
|
411
599
|
group.historySummary || "(none)",
|
|
412
600
|
"Constraints:",
|
|
413
601
|
constraints,
|
|
602
|
+
"Example JSON:",
|
|
603
|
+
"{\"operations\":[{\"op\":\"update_task\",\"taskKey\":\"web-01-us-01-t01\",\"updates\":{\"title\":\"Refined title\",\"storyPoints\":3}}]}",
|
|
414
604
|
"Return JSON ONLY matching: { \"operations\": [UpdateTaskOp | SplitTaskOp | MergeTasksOp | UpdateEstimateOp] } where each item has an `op` discriminator (update_task|split_task|merge_tasks|update_estimate).",
|
|
415
605
|
].join("\n\n");
|
|
416
606
|
}
|
|
@@ -528,6 +718,20 @@ export class RefineTasksService {
|
|
|
528
718
|
return existing;
|
|
529
719
|
return { ...(existing ?? {}), ...updates };
|
|
530
720
|
}
|
|
721
|
+
applyStageMetadata(metadata, content, shouldUpdate) {
|
|
722
|
+
if (!shouldUpdate)
|
|
723
|
+
return metadata;
|
|
724
|
+
const classification = classifyTask({
|
|
725
|
+
title: content.title,
|
|
726
|
+
description: content.description ?? undefined,
|
|
727
|
+
type: content.type ?? undefined,
|
|
728
|
+
});
|
|
729
|
+
return {
|
|
730
|
+
...(metadata ?? {}),
|
|
731
|
+
stage: classification.stage,
|
|
732
|
+
foundation: classification.foundation,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
531
735
|
validateOperation(group, op) {
|
|
532
736
|
const allowedOps = new Set(["update_task", "split_task", "merge_tasks", "update_estimate"]);
|
|
533
737
|
if (!op || typeof op.op !== "string" || !allowedOps.has(op.op)) {
|
|
@@ -634,6 +838,28 @@ export class RefineTasksService {
|
|
|
634
838
|
}
|
|
635
839
|
return false;
|
|
636
840
|
}
|
|
841
|
+
hasDependencyPath(graph, fromKey, toKey) {
|
|
842
|
+
if (fromKey === toKey)
|
|
843
|
+
return true;
|
|
844
|
+
const visited = new Set();
|
|
845
|
+
const stack = [fromKey];
|
|
846
|
+
while (stack.length > 0) {
|
|
847
|
+
const current = stack.pop();
|
|
848
|
+
if (current === toKey)
|
|
849
|
+
return true;
|
|
850
|
+
if (visited.has(current))
|
|
851
|
+
continue;
|
|
852
|
+
visited.add(current);
|
|
853
|
+
const neighbors = graph.get(current);
|
|
854
|
+
if (!neighbors)
|
|
855
|
+
continue;
|
|
856
|
+
for (const next of neighbors) {
|
|
857
|
+
if (!visited.has(next))
|
|
858
|
+
stack.push(next);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
637
863
|
async applyOperations(projectId, jobId, commandRunId, group, operations) {
|
|
638
864
|
const created = [];
|
|
639
865
|
const updated = [];
|
|
@@ -645,7 +871,6 @@ export class RefineTasksService {
|
|
|
645
871
|
let stage = "start";
|
|
646
872
|
const newTasks = [];
|
|
647
873
|
const pendingDeps = [];
|
|
648
|
-
const dependencyEdges = [];
|
|
649
874
|
try {
|
|
650
875
|
stage = "load:storyKeys";
|
|
651
876
|
const storyKeyRows = await this.workspaceRepo.getDb().all(`SELECT key FROM tasks WHERE user_story_id = ?`, group.story.id);
|
|
@@ -658,7 +883,13 @@ export class RefineTasksService {
|
|
|
658
883
|
if (!target)
|
|
659
884
|
continue;
|
|
660
885
|
const before = { ...target };
|
|
661
|
-
const
|
|
886
|
+
const mergedMetadata = this.mergeMetadata(target.metadata, op.updates.metadata);
|
|
887
|
+
const contentUpdated = op.updates.title !== undefined || op.updates.description !== undefined || op.updates.type !== undefined;
|
|
888
|
+
const metadata = this.applyStageMetadata(mergedMetadata, {
|
|
889
|
+
title: op.updates.title ?? target.title,
|
|
890
|
+
description: op.updates.description ?? target.description ?? null,
|
|
891
|
+
type: op.updates.type ?? target.type ?? null,
|
|
892
|
+
}, contentUpdated);
|
|
662
893
|
const beforeSp = target.storyPoints ?? 0;
|
|
663
894
|
const afterSp = op.updates.storyPoints ?? target.storyPoints ?? null;
|
|
664
895
|
storyPointsDelta += (afterSp ?? 0) - (beforeSp ?? 0);
|
|
@@ -687,13 +918,22 @@ export class RefineTasksService {
|
|
|
687
918
|
continue;
|
|
688
919
|
if (op.parentUpdates) {
|
|
689
920
|
const before = { ...target };
|
|
921
|
+
const mergedMetadata = this.mergeMetadata(target.metadata, op.parentUpdates.metadata);
|
|
922
|
+
const contentUpdated = op.parentUpdates.title !== undefined ||
|
|
923
|
+
op.parentUpdates.description !== undefined ||
|
|
924
|
+
op.parentUpdates.type !== undefined;
|
|
925
|
+
const metadata = this.applyStageMetadata(mergedMetadata, {
|
|
926
|
+
title: op.parentUpdates.title ?? target.title,
|
|
927
|
+
description: op.parentUpdates.description ?? target.description ?? null,
|
|
928
|
+
type: op.parentUpdates.type ?? target.type ?? null,
|
|
929
|
+
}, contentUpdated);
|
|
690
930
|
await this.workspaceRepo.updateTask(target.id, {
|
|
691
931
|
title: op.parentUpdates.title ?? target.title,
|
|
692
932
|
description: op.parentUpdates.description ?? target.description ?? null,
|
|
693
933
|
type: op.parentUpdates.type ?? target.type ?? null,
|
|
694
934
|
storyPoints: op.parentUpdates.storyPoints ?? target.storyPoints ?? null,
|
|
695
935
|
priority: op.parentUpdates.priority ?? target.priority ?? null,
|
|
696
|
-
metadata
|
|
936
|
+
metadata,
|
|
697
937
|
});
|
|
698
938
|
updated.push(target.key);
|
|
699
939
|
await this.workspaceRepo.insertTaskRevision({
|
|
@@ -705,7 +945,7 @@ export class RefineTasksService {
|
|
|
705
945
|
...before,
|
|
706
946
|
...op.parentUpdates,
|
|
707
947
|
storyPoints: op.parentUpdates.storyPoints ?? before.storyPoints,
|
|
708
|
-
metadata
|
|
948
|
+
metadata,
|
|
709
949
|
},
|
|
710
950
|
createdAt: new Date().toISOString(),
|
|
711
951
|
});
|
|
@@ -716,6 +956,13 @@ export class RefineTasksService {
|
|
|
716
956
|
if (childSp) {
|
|
717
957
|
storyPointsDelta += childSp;
|
|
718
958
|
}
|
|
959
|
+
const childMetadata = this.mergeMetadata({}, child.metadata);
|
|
960
|
+
const childContent = {
|
|
961
|
+
title: child.title,
|
|
962
|
+
description: child.description ?? target.description ?? "",
|
|
963
|
+
type: child.type ?? target.type ?? "feature",
|
|
964
|
+
};
|
|
965
|
+
const resolvedChildMetadata = this.applyStageMetadata(childMetadata, childContent, true);
|
|
719
966
|
const childInsert = {
|
|
720
967
|
projectId,
|
|
721
968
|
epicId: target.epicId,
|
|
@@ -727,7 +974,7 @@ export class RefineTasksService {
|
|
|
727
974
|
status: "not_started",
|
|
728
975
|
storyPoints: childSp,
|
|
729
976
|
priority: child.priority ?? target.priority ?? null,
|
|
730
|
-
metadata:
|
|
977
|
+
metadata: resolvedChildMetadata,
|
|
731
978
|
assignedAgentId: target.assignedAgentId ?? null,
|
|
732
979
|
assigneeHuman: target.assigneeHuman ?? null,
|
|
733
980
|
vcsBranch: null,
|
|
@@ -740,8 +987,12 @@ export class RefineTasksService {
|
|
|
740
987
|
for (const depKey of dependsOn) {
|
|
741
988
|
const depTask = taskByKey.get(depKey);
|
|
742
989
|
if (depTask) {
|
|
743
|
-
pendingDeps.push({
|
|
744
|
-
|
|
990
|
+
pendingDeps.push({
|
|
991
|
+
childKey,
|
|
992
|
+
dependsOnId: depTask.id,
|
|
993
|
+
dependsOnKey: depTask.key,
|
|
994
|
+
relationType: "blocks",
|
|
995
|
+
});
|
|
745
996
|
}
|
|
746
997
|
}
|
|
747
998
|
taskByKey.set(childKey, {
|
|
@@ -762,13 +1013,20 @@ export class RefineTasksService {
|
|
|
762
1013
|
continue;
|
|
763
1014
|
if (op.updates) {
|
|
764
1015
|
const before = { ...target };
|
|
1016
|
+
const mergedMetadata = this.mergeMetadata(target.metadata, op.updates.metadata);
|
|
1017
|
+
const contentUpdated = op.updates.title !== undefined || op.updates.description !== undefined || op.updates.type !== undefined;
|
|
1018
|
+
const metadata = this.applyStageMetadata(mergedMetadata, {
|
|
1019
|
+
title: op.updates.title ?? target.title,
|
|
1020
|
+
description: op.updates.description ?? target.description ?? null,
|
|
1021
|
+
type: op.updates.type ?? target.type ?? null,
|
|
1022
|
+
}, contentUpdated);
|
|
765
1023
|
await this.workspaceRepo.updateTask(target.id, {
|
|
766
1024
|
title: op.updates.title ?? target.title,
|
|
767
1025
|
description: op.updates.description ?? target.description ?? null,
|
|
768
1026
|
type: op.updates.type ?? target.type ?? null,
|
|
769
1027
|
storyPoints: op.updates.storyPoints ?? target.storyPoints ?? null,
|
|
770
1028
|
priority: op.updates.priority ?? target.priority ?? null,
|
|
771
|
-
metadata
|
|
1029
|
+
metadata,
|
|
772
1030
|
});
|
|
773
1031
|
updated.push(target.key);
|
|
774
1032
|
await this.workspaceRepo.insertTaskRevision({
|
|
@@ -780,7 +1038,7 @@ export class RefineTasksService {
|
|
|
780
1038
|
...before,
|
|
781
1039
|
...op.updates,
|
|
782
1040
|
storyPoints: op.updates.storyPoints ?? before.storyPoints,
|
|
783
|
-
metadata
|
|
1041
|
+
metadata,
|
|
784
1042
|
},
|
|
785
1043
|
createdAt: new Date().toISOString(),
|
|
786
1044
|
});
|
|
@@ -813,10 +1071,17 @@ export class RefineTasksService {
|
|
|
813
1071
|
const beforeSp = target.storyPoints ?? 0;
|
|
814
1072
|
const afterSp = op.storyPoints ?? target.storyPoints ?? null;
|
|
815
1073
|
storyPointsDelta += (afterSp ?? 0) - (beforeSp ?? 0);
|
|
1074
|
+
const contentUpdated = op.type !== undefined;
|
|
1075
|
+
const metadata = this.applyStageMetadata(target.metadata, {
|
|
1076
|
+
title: target.title,
|
|
1077
|
+
description: target.description ?? null,
|
|
1078
|
+
type: op.type ?? target.type ?? null,
|
|
1079
|
+
}, contentUpdated);
|
|
816
1080
|
await this.workspaceRepo.updateTask(target.id, {
|
|
817
1081
|
storyPoints: afterSp,
|
|
818
1082
|
type: op.type ?? target.type ?? null,
|
|
819
1083
|
priority: op.priority ?? target.priority ?? null,
|
|
1084
|
+
metadata: contentUpdated ? metadata : undefined,
|
|
820
1085
|
});
|
|
821
1086
|
updated.push(target.key);
|
|
822
1087
|
await this.workspaceRepo.insertTaskRevision({
|
|
@@ -824,11 +1089,30 @@ export class RefineTasksService {
|
|
|
824
1089
|
jobId,
|
|
825
1090
|
commandRunId,
|
|
826
1091
|
snapshotBefore: { ...target },
|
|
827
|
-
snapshotAfter: {
|
|
1092
|
+
snapshotAfter: {
|
|
1093
|
+
...target,
|
|
1094
|
+
storyPoints: afterSp,
|
|
1095
|
+
type: op.type ?? target.type ?? null,
|
|
1096
|
+
priority: op.priority ?? target.priority ?? null,
|
|
1097
|
+
metadata: contentUpdated ? metadata : target.metadata,
|
|
1098
|
+
},
|
|
828
1099
|
createdAt: new Date().toISOString(),
|
|
829
1100
|
});
|
|
830
1101
|
}
|
|
831
1102
|
}
|
|
1103
|
+
const dependencyGraph = new Map();
|
|
1104
|
+
const addEdge = (from, to) => {
|
|
1105
|
+
if (!from || !to)
|
|
1106
|
+
return;
|
|
1107
|
+
const edges = dependencyGraph.get(from) ?? new Set();
|
|
1108
|
+
edges.add(to);
|
|
1109
|
+
dependencyGraph.set(from, edges);
|
|
1110
|
+
};
|
|
1111
|
+
for (const task of group.tasks) {
|
|
1112
|
+
for (const dep of task.dependencies) {
|
|
1113
|
+
addEdge(task.key, dep);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
832
1116
|
if (newTasks.length > 0) {
|
|
833
1117
|
stage = "insert:newTasks";
|
|
834
1118
|
const inserted = await this.workspaceRepo.insertTasks(newTasks, false);
|
|
@@ -842,7 +1126,27 @@ export class RefineTasksService {
|
|
|
842
1126
|
}
|
|
843
1127
|
}
|
|
844
1128
|
const deps = [];
|
|
1129
|
+
const allowedDeps = [];
|
|
1130
|
+
const skippedDeps = [];
|
|
845
1131
|
for (const dep of pendingDeps) {
|
|
1132
|
+
if (!dep.dependsOnKey)
|
|
1133
|
+
continue;
|
|
1134
|
+
if (this.hasDependencyPath(dependencyGraph, dep.dependsOnKey, dep.childKey)) {
|
|
1135
|
+
skippedDeps.push({ childKey: dep.childKey, dependsOnKey: dep.dependsOnKey });
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
addEdge(dep.childKey, dep.dependsOnKey);
|
|
1139
|
+
allowedDeps.push(dep);
|
|
1140
|
+
}
|
|
1141
|
+
if (skippedDeps.length > 0) {
|
|
1142
|
+
const sample = skippedDeps
|
|
1143
|
+
.slice(0, 5)
|
|
1144
|
+
.map((dep) => `${dep.childKey}->${dep.dependsOnKey}`)
|
|
1145
|
+
.join(", ");
|
|
1146
|
+
warnings.push(`Skipped ${skippedDeps.length} refine dependencies that would create cycles.` +
|
|
1147
|
+
(sample ? ` Sample: ${sample}` : ""));
|
|
1148
|
+
}
|
|
1149
|
+
for (const dep of allowedDeps) {
|
|
846
1150
|
const childId = idByKey.get(dep.childKey);
|
|
847
1151
|
if (childId) {
|
|
848
1152
|
deps.push({ taskId: childId, dependsOnTaskId: dep.dependsOnId, relationType: dep.relationType });
|
|
@@ -855,12 +1159,11 @@ export class RefineTasksService {
|
|
|
855
1159
|
}
|
|
856
1160
|
// cycle detection on current + new dependencies (by key)
|
|
857
1161
|
const edgeSet = [];
|
|
858
|
-
for (const
|
|
859
|
-
for (const dep of
|
|
860
|
-
edgeSet.push({ from
|
|
1162
|
+
for (const [from, deps] of dependencyGraph.entries()) {
|
|
1163
|
+
for (const dep of deps) {
|
|
1164
|
+
edgeSet.push({ from, to: dep });
|
|
861
1165
|
}
|
|
862
1166
|
}
|
|
863
|
-
edgeSet.push(...dependencyEdges);
|
|
864
1167
|
const hasCycle = this.detectCycle(edgeSet);
|
|
865
1168
|
if (hasCycle) {
|
|
866
1169
|
throw new Error("Dependency cycle detected after refinement; aborting apply.");
|
|
@@ -987,9 +1290,15 @@ export class RefineTasksService {
|
|
|
987
1290
|
tokensTotal: promptTokens + completionTokens,
|
|
988
1291
|
durationSeconds,
|
|
989
1292
|
timestamp: new Date().toISOString(),
|
|
990
|
-
metadata: {
|
|
1293
|
+
metadata: {
|
|
1294
|
+
command: "refine-tasks",
|
|
1295
|
+
action: "agent_refine",
|
|
1296
|
+
phase: "agent_refine",
|
|
1297
|
+
attempt: 1,
|
|
1298
|
+
...(metadata ?? {}),
|
|
1299
|
+
},
|
|
991
1300
|
});
|
|
992
|
-
return { raw: output, promptTokens, completionTokens };
|
|
1301
|
+
return { raw: output, promptTokens, completionTokens, agentId: agent.id };
|
|
993
1302
|
}
|
|
994
1303
|
async refineTasks(options) {
|
|
995
1304
|
const strategy = options.strategy ?? DEFAULT_STRATEGY;
|
|
@@ -1025,6 +1334,30 @@ export class RefineTasksService {
|
|
|
1025
1334
|
planOut: options.planOutPath,
|
|
1026
1335
|
},
|
|
1027
1336
|
});
|
|
1337
|
+
let ratingAgentId;
|
|
1338
|
+
const maybeRateAgent = async () => {
|
|
1339
|
+
if (!options.rateAgents || !ratingAgentId)
|
|
1340
|
+
return;
|
|
1341
|
+
try {
|
|
1342
|
+
const ratingService = this.ensureRatingService();
|
|
1343
|
+
await ratingService.rate({
|
|
1344
|
+
workspace: this.workspace,
|
|
1345
|
+
agentId: ratingAgentId,
|
|
1346
|
+
commandName: "refine-tasks",
|
|
1347
|
+
jobId: job.id,
|
|
1348
|
+
commandRunId: commandRun.id,
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
catch (error) {
|
|
1352
|
+
const message = `Agent rating failed: ${error.message ?? String(error)}`;
|
|
1353
|
+
try {
|
|
1354
|
+
await this.jobService.appendLog(job.id, `${message}\n`);
|
|
1355
|
+
}
|
|
1356
|
+
catch {
|
|
1357
|
+
/* ignore rating log failures */
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1028
1361
|
try {
|
|
1029
1362
|
if (options.fromDb === false) {
|
|
1030
1363
|
throw new Error("refine-tasks currently only supports DB-backed selection; set --from-db true");
|
|
@@ -1166,17 +1499,30 @@ export class RefineTasksService {
|
|
|
1166
1499
|
await this.logWarningsToTasks(group.tasks.map((t) => t.id), job.id, commandRun.id, docWarnings.join("; "));
|
|
1167
1500
|
}
|
|
1168
1501
|
const prompt = this.buildStoryPrompt(group, strategy, docSummary);
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1502
|
+
const parseOps = (raw) => {
|
|
1503
|
+
const parsed = normalizePlanJson(extractJson(raw));
|
|
1504
|
+
const ops = parsed?.operations && Array.isArray(parsed.operations) ? parsed.operations : [];
|
|
1505
|
+
const normalized = ops.map(normalizeOperation);
|
|
1506
|
+
return normalized.filter((op) => {
|
|
1507
|
+
const { valid, reason } = this.validateOperation(group, op);
|
|
1508
|
+
if (!valid && reason) {
|
|
1509
|
+
plan.warnings?.push(`Skipped op for story ${group.story.key}: ${reason}`);
|
|
1510
|
+
}
|
|
1511
|
+
return valid;
|
|
1512
|
+
});
|
|
1513
|
+
};
|
|
1514
|
+
const { raw, agentId } = await this.invokeAgent(options.agentName, prompt, agentStream, job.id, commandRun.id, { epicKey: group.epic.key, storyKey: group.story.key });
|
|
1515
|
+
ratingAgentId = agentId;
|
|
1516
|
+
let filtered = parseOps(raw);
|
|
1517
|
+
if (filtered.length === 0) {
|
|
1518
|
+
const retryPrompt = `${prompt}\n\nRETRY: Your previous response did not match the JSON schema. Return only a JSON object with an operations array (no prose, no markdown, no <think> tags).`;
|
|
1519
|
+
const retry = await this.invokeAgent(options.agentName, retryPrompt, agentStream, job.id, commandRun.id, { epicKey: group.epic.key, storyKey: group.story.key, retry: true });
|
|
1520
|
+
ratingAgentId = retry.agentId;
|
|
1521
|
+
filtered = parseOps(retry.raw);
|
|
1522
|
+
if (filtered.length === 0) {
|
|
1523
|
+
plan.warnings?.push(`No valid operations returned for story ${group.story.key}.`);
|
|
1177
1524
|
}
|
|
1178
|
-
|
|
1179
|
-
});
|
|
1525
|
+
}
|
|
1180
1526
|
plan.operations.push(...filtered);
|
|
1181
1527
|
}
|
|
1182
1528
|
catch (error) {
|
|
@@ -1198,7 +1544,7 @@ export class RefineTasksService {
|
|
|
1198
1544
|
return candidate;
|
|
1199
1545
|
}
|
|
1200
1546
|
};
|
|
1201
|
-
const defaultPlanPath = path.join(this.workspace.
|
|
1547
|
+
const defaultPlanPath = path.join(this.workspace.mcodaDir, "tasks", options.projectKey, "refinements", job.id, "plan.json");
|
|
1202
1548
|
const requestedOutPath = options.planOutPath ? path.resolve(options.planOutPath) : defaultPlanPath;
|
|
1203
1549
|
const outPath = await ensureUniquePath(requestedOutPath);
|
|
1204
1550
|
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
@@ -1222,6 +1568,7 @@ export class RefineTasksService {
|
|
|
1222
1568
|
lastCheckpoint: "no_operations",
|
|
1223
1569
|
});
|
|
1224
1570
|
await this.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
1571
|
+
await maybeRateAgent();
|
|
1225
1572
|
return {
|
|
1226
1573
|
jobId: job.id,
|
|
1227
1574
|
commandRunId: commandRun.id,
|
|
@@ -1246,6 +1593,7 @@ export class RefineTasksService {
|
|
|
1246
1593
|
lastCheckpoint: "dry_run",
|
|
1247
1594
|
});
|
|
1248
1595
|
await this.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
1596
|
+
await maybeRateAgent();
|
|
1249
1597
|
return {
|
|
1250
1598
|
jobId: job.id,
|
|
1251
1599
|
commandRunId: commandRun.id,
|
|
@@ -1291,6 +1639,7 @@ export class RefineTasksService {
|
|
|
1291
1639
|
cancelled.push(...x);
|
|
1292
1640
|
storyPointsDelta += delta;
|
|
1293
1641
|
}
|
|
1642
|
+
await this.seedPriorities(options.projectKey);
|
|
1294
1643
|
await this.jobService.updateJobStatus(job.id, "completed", {
|
|
1295
1644
|
payload: {
|
|
1296
1645
|
created: created.length,
|
|
@@ -1303,6 +1652,7 @@ export class RefineTasksService {
|
|
|
1303
1652
|
lastCheckpoint: "completed",
|
|
1304
1653
|
});
|
|
1305
1654
|
await this.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
1655
|
+
await maybeRateAgent();
|
|
1306
1656
|
return {
|
|
1307
1657
|
jobId: job.id,
|
|
1308
1658
|
commandRunId: commandRun.id,
|