@mcoda/core 0.1.18 → 0.1.20
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/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +3 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +22 -8
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +53 -34
- package/dist/services/backlog/BacklogService.d.ts.map +1 -1
- package/dist/services/backlog/BacklogService.js +3 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +9 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +251 -35
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +487 -71
- package/dist/services/docs/review/gates/PdrFolderTreeGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrFolderTreeGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrFolderTreeGate.js +151 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.js +109 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.js +128 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.js +153 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.js +109 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.js +128 -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 +54 -0
- package/dist/services/execution/QaTasksService.d.ts +6 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +278 -95
- package/dist/services/execution/TaskSelectionService.d.ts +3 -0
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +33 -0
- package/dist/services/execution/WorkOnTasksService.d.ts +4 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +146 -22
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +43 -4
- package/dist/services/planning/CreateTasksService.d.ts +15 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +592 -81
- package/dist/services/planning/RefineTasksService.d.ts +1 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +88 -2
- package/dist/services/review/CodeReviewService.d.ts +6 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +260 -41
- package/dist/services/shared/ProjectGuidance.d.ts +18 -2
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -1
- package/dist/services/shared/ProjectGuidance.js +535 -34
- package/package.json +6 -6
|
@@ -3,6 +3,7 @@ import { AgentService } from "@mcoda/agents";
|
|
|
3
3
|
import { DocdexClient } from "@mcoda/integrations";
|
|
4
4
|
import { GlobalRepository, WorkspaceRepository, Connection } from "@mcoda/db";
|
|
5
5
|
import { PathHelper, READY_TO_CODE_REVIEW, normalizeReviewStatuses } from "@mcoda/shared";
|
|
6
|
+
import YAML from "yaml";
|
|
6
7
|
import { JobService } from "../jobs/JobService.js";
|
|
7
8
|
import { RoutingService } from "../agents/RoutingService.js";
|
|
8
9
|
import { classifyTask } from "./TaskOrderingHeuristics.js";
|
|
@@ -10,6 +11,8 @@ const DEFAULT_STATUSES = ["not_started", "in_progress", "changes_requested", REA
|
|
|
10
11
|
const DONE_STATUSES = new Set(["completed", "cancelled"]);
|
|
11
12
|
const DEFAULT_STAGE_ORDER = ["foundation", "backend", "frontend", "other"];
|
|
12
13
|
const PLANNING_DOC_HINT_PATTERN = /(sds|pdr|rfp|requirements|architecture|openapi|swagger|design)/i;
|
|
14
|
+
const OPENAPI_METHODS = new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
15
|
+
const OPENAPI_HINTS_LIMIT = 20;
|
|
13
16
|
const STATUS_RANK = {
|
|
14
17
|
in_progress: 0,
|
|
15
18
|
changes_requested: 0,
|
|
@@ -31,6 +34,28 @@ const normalizeStatuses = (statuses) => {
|
|
|
31
34
|
return normalizeReviewStatuses(normalized);
|
|
32
35
|
};
|
|
33
36
|
const estimateTokens = (text) => Math.max(1, Math.ceil((text ?? "").length / 4));
|
|
37
|
+
const isPlainObject = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
38
|
+
const parseStructuredDoc = (raw) => {
|
|
39
|
+
if (!raw || raw.trim().length === 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
try {
|
|
42
|
+
const parsed = YAML.parse(raw);
|
|
43
|
+
if (isPlainObject(parsed))
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// continue
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
if (isPlainObject(parsed))
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// ignore invalid parse
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
34
59
|
const SDS_DEPENDENCY_GUIDE = [
|
|
35
60
|
"SDS hints for dependency-aware ordering:",
|
|
36
61
|
"- Enforce topological ordering: never place a task before any of its dependencies.",
|
|
@@ -202,6 +227,77 @@ export class TaskOrderingService {
|
|
|
202
227
|
this.docdex = docdex;
|
|
203
228
|
this.recordTelemetry = recordTelemetry;
|
|
204
229
|
}
|
|
230
|
+
classifyDocContextKind(doc) {
|
|
231
|
+
const type = (doc.docType ?? "").toLowerCase();
|
|
232
|
+
const label = `${doc.path ?? ""} ${doc.title ?? ""}`.toLowerCase();
|
|
233
|
+
if (type.includes("sds") || /\bsds\b/.test(label))
|
|
234
|
+
return "sds";
|
|
235
|
+
if (type.includes("openapi") || type.includes("swagger") || /(openapi|swagger)/.test(label))
|
|
236
|
+
return "openapi";
|
|
237
|
+
return "fallback";
|
|
238
|
+
}
|
|
239
|
+
buildOpenApiHintSummary(docs) {
|
|
240
|
+
const lines = [];
|
|
241
|
+
const countEntries = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string").length : 0;
|
|
242
|
+
for (const doc of docs) {
|
|
243
|
+
if (this.classifyDocContextKind(doc) !== "openapi")
|
|
244
|
+
continue;
|
|
245
|
+
const rawContent = doc.content && doc.content.trim().length > 0 ? doc.content : (doc.segments ?? []).map((segment) => segment.content).join("\n\n");
|
|
246
|
+
const parsed = parseStructuredDoc(rawContent);
|
|
247
|
+
if (!parsed)
|
|
248
|
+
continue;
|
|
249
|
+
const paths = parsed.paths;
|
|
250
|
+
if (!isPlainObject(paths))
|
|
251
|
+
continue;
|
|
252
|
+
for (const [apiPath, pathItem] of Object.entries(paths)) {
|
|
253
|
+
if (!isPlainObject(pathItem))
|
|
254
|
+
continue;
|
|
255
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
256
|
+
const normalizedMethod = method.toLowerCase();
|
|
257
|
+
if (!OPENAPI_METHODS.has(normalizedMethod))
|
|
258
|
+
continue;
|
|
259
|
+
if (!isPlainObject(operation))
|
|
260
|
+
continue;
|
|
261
|
+
const hints = operation["x-mcoda-task-hints"];
|
|
262
|
+
if (!isPlainObject(hints))
|
|
263
|
+
continue;
|
|
264
|
+
const service = typeof hints.service === "string" ? hints.service : "-";
|
|
265
|
+
const capability = typeof hints.capability === "string" ? hints.capability : "-";
|
|
266
|
+
const stage = typeof hints.stage === "string" ? hints.stage : "-";
|
|
267
|
+
const complexity = typeof hints.complexity === "number" && Number.isFinite(hints.complexity) ? hints.complexity.toFixed(1) : "-";
|
|
268
|
+
const dependsOn = Array.isArray(hints.depends_on_operations)
|
|
269
|
+
? hints.depends_on_operations.filter((entry) => typeof entry === "string").length
|
|
270
|
+
: 0;
|
|
271
|
+
const tests = isPlainObject(hints.test_requirements) ? hints.test_requirements : undefined;
|
|
272
|
+
const unitCount = countEntries(tests?.unit);
|
|
273
|
+
const componentCount = countEntries(tests?.component);
|
|
274
|
+
const integrationCount = countEntries(tests?.integration);
|
|
275
|
+
const apiCount = countEntries(tests?.api);
|
|
276
|
+
lines.push(`- ${normalizedMethod.toUpperCase()} ${apiPath} :: service=${service}; capability=${capability}; stage=${stage}; complexity=${complexity}; deps=${dependsOn}; tests(u/c/i/a)=${unitCount}/${componentCount}/${integrationCount}/${apiCount}`);
|
|
277
|
+
if (lines.length >= OPENAPI_HINTS_LIMIT) {
|
|
278
|
+
return lines.join("\n");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return lines.join("\n");
|
|
284
|
+
}
|
|
285
|
+
enforcePlanningContextPolicy(policy, context) {
|
|
286
|
+
if (policy === "best_effort")
|
|
287
|
+
return;
|
|
288
|
+
if (policy === "require_any") {
|
|
289
|
+
if (!context) {
|
|
290
|
+
throw new Error("Planning context is required but no planning documents were resolved (policy=require_any).");
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (!context) {
|
|
295
|
+
throw new Error("Planning context is required from SDS/OpenAPI sources, but none were resolved (policy=require_sds_or_openapi).");
|
|
296
|
+
}
|
|
297
|
+
if (context.kind !== "sds" && context.kind !== "openapi") {
|
|
298
|
+
throw new Error(`Planning context policy require_sds_or_openapi rejected source '${context.source}' (kind=${context.kind}).`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
205
301
|
static async create(workspace, options = {}) {
|
|
206
302
|
const dbPath = PathHelper.getWorkspaceDbPath(workspace.workspaceRoot);
|
|
207
303
|
try {
|
|
@@ -232,11 +328,14 @@ export class TaskOrderingService {
|
|
|
232
328
|
async buildDocContext(projectKey, warnings) {
|
|
233
329
|
try {
|
|
234
330
|
let docs = await this.docdex.search({ docType: "SDS", projectKey });
|
|
331
|
+
if (!docs.length) {
|
|
332
|
+
docs = await this.docdex.search({ docType: "OPENAPI", projectKey });
|
|
333
|
+
}
|
|
235
334
|
if (!docs.length) {
|
|
236
335
|
docs = await this.docdex.search({
|
|
237
336
|
projectKey,
|
|
238
337
|
profile: "workspace-code",
|
|
239
|
-
query: "sds requirements architecture openapi",
|
|
338
|
+
query: "sds requirements architecture openapi swagger",
|
|
240
339
|
});
|
|
241
340
|
}
|
|
242
341
|
if (!docs.length)
|
|
@@ -246,6 +345,7 @@ export class TaskOrderingService {
|
|
|
246
345
|
const label = `${entry.path ?? ""} ${entry.title ?? ""}`.toLowerCase();
|
|
247
346
|
return type.includes("sds") || type.includes("pdr") || type.includes("rfp") || PLANNING_DOC_HINT_PATTERN.test(label);
|
|
248
347
|
}) ?? docs[0];
|
|
348
|
+
const kind = this.classifyDocContextKind(doc);
|
|
249
349
|
const segments = (doc.segments ?? []).slice(0, 3);
|
|
250
350
|
const body = segments.length > 0
|
|
251
351
|
? segments
|
|
@@ -256,9 +356,15 @@ export class TaskOrderingService {
|
|
|
256
356
|
})
|
|
257
357
|
.join("\n\n")
|
|
258
358
|
: doc.content ?? "";
|
|
359
|
+
const openApiHints = this.buildOpenApiHintSummary([doc]);
|
|
360
|
+
const contextBlocks = ["[Planning context]", doc.title ?? doc.path ?? doc.id, body];
|
|
361
|
+
if (openApiHints) {
|
|
362
|
+
contextBlocks.push(`[OPENAPI_HINTS]\n${openApiHints}`);
|
|
363
|
+
}
|
|
259
364
|
return {
|
|
260
|
-
content:
|
|
365
|
+
content: contextBlocks.filter(Boolean).join("\n\n"),
|
|
261
366
|
source: doc.id ?? doc.path ?? "sds",
|
|
367
|
+
kind,
|
|
262
368
|
};
|
|
263
369
|
}
|
|
264
370
|
catch (error) {
|
|
@@ -437,6 +543,67 @@ export class TaskOrderingService {
|
|
|
437
543
|
const inferred = classifyTask({ title: task.title, description: task.description, type: task.type ?? undefined });
|
|
438
544
|
return { stage: inferred.stage, foundation: inferred.foundation };
|
|
439
545
|
}
|
|
546
|
+
complexityBand(score) {
|
|
547
|
+
if (score < 12)
|
|
548
|
+
return "low";
|
|
549
|
+
if (score < 24)
|
|
550
|
+
return "medium";
|
|
551
|
+
if (score < 40)
|
|
552
|
+
return "high";
|
|
553
|
+
return "very_high";
|
|
554
|
+
}
|
|
555
|
+
buildOrderingMetadata(tasks, impact, missingContext, docContext) {
|
|
556
|
+
const metadataByTask = new Map();
|
|
557
|
+
const complexityByTask = new Map();
|
|
558
|
+
for (const task of tasks) {
|
|
559
|
+
const classification = this.resolveClassification(task);
|
|
560
|
+
const inferred = classifyTask({ title: task.title, description: task.description, type: task.type ?? undefined });
|
|
561
|
+
const impactEntry = impact.get(task.id) ?? { direct: 0, total: 0 };
|
|
562
|
+
const dependencyCount = task.dependencies.length;
|
|
563
|
+
const missingContextOpen = missingContext.has(task.id);
|
|
564
|
+
const textLength = `${task.title ?? ""} ${task.description ?? ""}`.trim().length;
|
|
565
|
+
const textWeight = Math.min(6, Math.ceil(textLength / 200));
|
|
566
|
+
const stageWeight = classification.stage === "backend" ? 2 : classification.stage === "frontend" ? 1.5 : classification.stage === "foundation" ? 1.2 : 1;
|
|
567
|
+
let complexityScore = (task.story_points ?? 0) * 5 +
|
|
568
|
+
impactEntry.total * 3 +
|
|
569
|
+
dependencyCount * 2 +
|
|
570
|
+
textWeight * stageWeight +
|
|
571
|
+
(classification.foundation ? 2 : 0) +
|
|
572
|
+
(missingContextOpen ? 4 : 0);
|
|
573
|
+
complexityScore = Number(Math.max(1, complexityScore).toFixed(2));
|
|
574
|
+
complexityByTask.set(task.id, complexityScore);
|
|
575
|
+
const existingMetadata = task.metadata ?? {};
|
|
576
|
+
const existingOrdering = existingMetadata && typeof existingMetadata.ordering === "object" && !Array.isArray(existingMetadata.ordering)
|
|
577
|
+
? existingMetadata.ordering
|
|
578
|
+
: {};
|
|
579
|
+
const reasons = [...inferred.reasons];
|
|
580
|
+
if (typeof existingMetadata.stage === "string") {
|
|
581
|
+
reasons.unshift(`metadata:stage:${String(existingMetadata.stage).toLowerCase()}`);
|
|
582
|
+
}
|
|
583
|
+
if (typeof existingMetadata.foundation === "boolean") {
|
|
584
|
+
reasons.unshift(`metadata:foundation:${existingMetadata.foundation ? "true" : "false"}`);
|
|
585
|
+
}
|
|
586
|
+
const mergedMetadata = {
|
|
587
|
+
...existingMetadata,
|
|
588
|
+
stage: classification.stage,
|
|
589
|
+
foundation: classification.foundation,
|
|
590
|
+
ordering: {
|
|
591
|
+
...existingOrdering,
|
|
592
|
+
stage: classification.stage,
|
|
593
|
+
foundation: classification.foundation,
|
|
594
|
+
dependencyImpact: impactEntry,
|
|
595
|
+
dependencyCount,
|
|
596
|
+
complexityScore,
|
|
597
|
+
complexityBand: this.complexityBand(complexityScore),
|
|
598
|
+
missingContextOpen,
|
|
599
|
+
classificationReasons: reasons,
|
|
600
|
+
docContextSource: docContext?.source,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
metadataByTask.set(task.id, mergedMetadata);
|
|
604
|
+
}
|
|
605
|
+
return { metadataByTask, complexityByTask };
|
|
606
|
+
}
|
|
440
607
|
buildDependencyGraph(tasks, deps) {
|
|
441
608
|
const taskIds = new Set(tasks.map((task) => task.id));
|
|
442
609
|
const graph = new Map();
|
|
@@ -478,7 +645,7 @@ export class TaskOrderingService {
|
|
|
478
645
|
}
|
|
479
646
|
return false;
|
|
480
647
|
}
|
|
481
|
-
async injectFoundationDependencies(tasks, deps, warnings) {
|
|
648
|
+
async injectFoundationDependencies(tasks, deps, warnings, persist) {
|
|
482
649
|
const classification = new Map();
|
|
483
650
|
for (const task of tasks) {
|
|
484
651
|
classification.set(task.id, this.resolveClassification(task));
|
|
@@ -528,7 +695,9 @@ export class TaskOrderingService {
|
|
|
528
695
|
}
|
|
529
696
|
return;
|
|
530
697
|
}
|
|
531
|
-
|
|
698
|
+
if (persist) {
|
|
699
|
+
await this.repo.insertTaskDependencies(inserts, true);
|
|
700
|
+
}
|
|
532
701
|
for (const insert of inserts) {
|
|
533
702
|
const depList = deps.get(insert.taskId) ?? [];
|
|
534
703
|
const dependsOn = taskById.get(insert.dependsOnTaskId);
|
|
@@ -540,7 +709,12 @@ export class TaskOrderingService {
|
|
|
540
709
|
});
|
|
541
710
|
deps.set(insert.taskId, depList);
|
|
542
711
|
}
|
|
543
|
-
|
|
712
|
+
if (persist) {
|
|
713
|
+
warnings.push(`Injected ${inserts.length} inferred foundation deps.`);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
warnings.push(`Dry run: inferred ${inserts.length} foundation deps (not persisted).`);
|
|
717
|
+
}
|
|
544
718
|
if (skippedCycles > 0) {
|
|
545
719
|
warnings.push(`Skipped ${skippedCycles} inferred foundation deps due to cycles.`);
|
|
546
720
|
if (skippedEdges.length > 0) {
|
|
@@ -548,7 +722,7 @@ export class TaskOrderingService {
|
|
|
548
722
|
}
|
|
549
723
|
}
|
|
550
724
|
}
|
|
551
|
-
async applyInferredDependencies(tasks, deps, inferred, warnings) {
|
|
725
|
+
async applyInferredDependencies(tasks, deps, inferred, warnings, persist) {
|
|
552
726
|
if (inferred.length === 0)
|
|
553
727
|
return;
|
|
554
728
|
const taskByKey = new Map(tasks.map((task) => [task.key, task]));
|
|
@@ -598,7 +772,9 @@ export class TaskOrderingService {
|
|
|
598
772
|
}
|
|
599
773
|
return;
|
|
600
774
|
}
|
|
601
|
-
|
|
775
|
+
if (persist) {
|
|
776
|
+
await this.repo.insertTaskDependencies(inserts, true);
|
|
777
|
+
}
|
|
602
778
|
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
603
779
|
for (const insert of inserts) {
|
|
604
780
|
const depList = deps.get(insert.taskId) ?? [];
|
|
@@ -611,7 +787,12 @@ export class TaskOrderingService {
|
|
|
611
787
|
});
|
|
612
788
|
deps.set(insert.taskId, depList);
|
|
613
789
|
}
|
|
614
|
-
|
|
790
|
+
if (persist) {
|
|
791
|
+
warnings.push(`Applied ${inserts.length} inferred agent deps.`);
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
warnings.push(`Dry run: inferred ${inserts.length} agent deps (not persisted).`);
|
|
795
|
+
}
|
|
615
796
|
if (skippedCycles > 0) {
|
|
616
797
|
warnings.push(`Skipped ${skippedCycles} inferred agent deps due to cycles.`);
|
|
617
798
|
if (skippedEdges.length > 0) {
|
|
@@ -619,7 +800,7 @@ export class TaskOrderingService {
|
|
|
619
800
|
}
|
|
620
801
|
}
|
|
621
802
|
}
|
|
622
|
-
compareTasks(a, b, impact, agentRank, stageOrderMap) {
|
|
803
|
+
compareTasks(a, b, impact, complexityByTask, missingContext, agentRank, stageOrderMap) {
|
|
623
804
|
const epicPriorityA = a.epic_priority ?? Number.MAX_SAFE_INTEGER;
|
|
624
805
|
const epicPriorityB = b.epic_priority ?? Number.MAX_SAFE_INTEGER;
|
|
625
806
|
if (epicPriorityA !== epicPriorityB)
|
|
@@ -628,14 +809,29 @@ export class TaskOrderingService {
|
|
|
628
809
|
const storyPriorityB = b.story_priority ?? Number.MAX_SAFE_INTEGER;
|
|
629
810
|
if (storyPriorityA !== storyPriorityB)
|
|
630
811
|
return storyPriorityA - storyPriorityB;
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
634
|
-
return
|
|
812
|
+
const missingA = missingContext.has(a.id);
|
|
813
|
+
const missingB = missingContext.has(b.id);
|
|
814
|
+
if (missingA !== missingB)
|
|
815
|
+
return missingA ? 1 : -1;
|
|
816
|
+
const classA = this.resolveClassification(a);
|
|
817
|
+
const classB = this.resolveClassification(b);
|
|
818
|
+
if (classA.foundation !== classB.foundation) {
|
|
819
|
+
return classA.foundation ? -1 : 1;
|
|
820
|
+
}
|
|
821
|
+
if (stageOrderMap) {
|
|
822
|
+
const stageA = stageOrderMap.get(classA.stage) ?? stageOrderMap.get("other") ?? Number.MAX_SAFE_INTEGER;
|
|
823
|
+
const stageB = stageOrderMap.get(classB.stage) ?? stageOrderMap.get("other") ?? Number.MAX_SAFE_INTEGER;
|
|
824
|
+
if (stageA !== stageB)
|
|
825
|
+
return stageA - stageB;
|
|
826
|
+
}
|
|
635
827
|
const impactA = impact.get(a.id)?.total ?? 0;
|
|
636
828
|
const impactB = impact.get(b.id)?.total ?? 0;
|
|
637
829
|
if (impactA !== impactB)
|
|
638
830
|
return impactB - impactA;
|
|
831
|
+
const complexityA = complexityByTask.get(a.id) ?? 0;
|
|
832
|
+
const complexityB = complexityByTask.get(b.id) ?? 0;
|
|
833
|
+
if (complexityA !== complexityB)
|
|
834
|
+
return complexityB - complexityA;
|
|
639
835
|
const rankA = agentRank?.get(a.id);
|
|
640
836
|
const rankB = agentRank?.get(b.id);
|
|
641
837
|
if (rankA !== undefined || rankB !== undefined) {
|
|
@@ -646,17 +842,10 @@ export class TaskOrderingService {
|
|
|
646
842
|
if (rankA !== rankB)
|
|
647
843
|
return rankA - rankB;
|
|
648
844
|
}
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
if (
|
|
652
|
-
return
|
|
653
|
-
}
|
|
654
|
-
if (stageOrderMap) {
|
|
655
|
-
const stageA = stageOrderMap.get(classA.stage) ?? stageOrderMap.get("other") ?? Number.MAX_SAFE_INTEGER;
|
|
656
|
-
const stageB = stageOrderMap.get(classB.stage) ?? stageOrderMap.get("other") ?? Number.MAX_SAFE_INTEGER;
|
|
657
|
-
if (stageA !== stageB)
|
|
658
|
-
return stageA - stageB;
|
|
659
|
-
}
|
|
845
|
+
const priorityA = a.priority ?? Number.MAX_SAFE_INTEGER;
|
|
846
|
+
const priorityB = b.priority ?? Number.MAX_SAFE_INTEGER;
|
|
847
|
+
if (priorityA !== priorityB)
|
|
848
|
+
return priorityA - priorityB;
|
|
660
849
|
const spA = a.story_points ?? Number.POSITIVE_INFINITY;
|
|
661
850
|
const spB = b.story_points ?? Number.POSITIVE_INFINITY;
|
|
662
851
|
if (spA !== spB)
|
|
@@ -671,7 +860,7 @@ export class TaskOrderingService {
|
|
|
671
860
|
return statusA - statusB;
|
|
672
861
|
return a.key.localeCompare(b.key);
|
|
673
862
|
}
|
|
674
|
-
topologicalSort(tasks, edges, impact, agentRank, stageOrderMap) {
|
|
863
|
+
topologicalSort(tasks, edges, impact, complexityByTask, missingContext, agentRank, stageOrderMap) {
|
|
675
864
|
const indegree = new Map();
|
|
676
865
|
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
677
866
|
for (const task of tasks) {
|
|
@@ -685,7 +874,7 @@ export class TaskOrderingService {
|
|
|
685
874
|
}
|
|
686
875
|
}
|
|
687
876
|
const queue = tasks.filter((t) => (indegree.get(t.id) ?? 0) === 0);
|
|
688
|
-
const sortQueue = () => queue.sort((a, b) => this.compareTasks(a, b, impact, agentRank, stageOrderMap));
|
|
877
|
+
const sortQueue = () => queue.sort((a, b) => this.compareTasks(a, b, impact, complexityByTask, missingContext, agentRank, stageOrderMap));
|
|
689
878
|
sortQueue();
|
|
690
879
|
const ordered = [];
|
|
691
880
|
const visited = new Set();
|
|
@@ -713,7 +902,7 @@ export class TaskOrderingService {
|
|
|
713
902
|
}
|
|
714
903
|
}
|
|
715
904
|
const remaining = tasks.filter((t) => !visited.has(t.id));
|
|
716
|
-
remaining.sort((a, b) => this.compareTasks(a, b, impact, agentRank, stageOrderMap));
|
|
905
|
+
remaining.sort((a, b) => this.compareTasks(a, b, impact, complexityByTask, missingContext, agentRank, stageOrderMap));
|
|
717
906
|
ordered.push(...remaining);
|
|
718
907
|
}
|
|
719
908
|
return { ordered, cycle, cycleMembers };
|
|
@@ -854,11 +1043,15 @@ export class TaskOrderingService {
|
|
|
854
1043
|
const taskKeys = new Set(tasks.map((task) => task.key));
|
|
855
1044
|
return parseDependencyInferenceOutput(output, taskKeys, context.warnings);
|
|
856
1045
|
}
|
|
857
|
-
async persistPriorities(ordered, epicMap, storyMap) {
|
|
1046
|
+
async persistPriorities(ordered, epicMap, storyMap, metadataByTask) {
|
|
858
1047
|
await this.repo.withTransaction(async () => {
|
|
859
1048
|
for (let i = 0; i < ordered.length; i += 1) {
|
|
860
1049
|
const task = ordered[i];
|
|
861
|
-
|
|
1050
|
+
const nextMetadata = metadataByTask?.get(task.id);
|
|
1051
|
+
await this.repo.updateTask(task.id, {
|
|
1052
|
+
priority: i + 1,
|
|
1053
|
+
metadata: nextMetadata !== undefined ? nextMetadata : undefined,
|
|
1054
|
+
});
|
|
862
1055
|
}
|
|
863
1056
|
const epicEntries = Array.from(epicMap.entries()).map(([epicId, tasks]) => ({
|
|
864
1057
|
epicId,
|
|
@@ -943,10 +1136,13 @@ export class TaskOrderingService {
|
|
|
943
1136
|
if (request.storyKey && !story) {
|
|
944
1137
|
throw new Error(`Unknown user story key: ${request.storyKey} for project ${request.projectKey}`);
|
|
945
1138
|
}
|
|
1139
|
+
const applyChanges = request.apply !== false;
|
|
1140
|
+
const enrichMetadata = request.enrichMetadata !== false;
|
|
1141
|
+
const planningContextPolicy = request.planningContextPolicy ?? "best_effort";
|
|
946
1142
|
const tasks = await this.fetchTasks(project.id, epic?.id, statuses, story?.id, request.assignee);
|
|
947
1143
|
const deps = await this.fetchDependencies(tasks.map((t) => t.id));
|
|
948
1144
|
if (request.injectFoundationDeps !== false) {
|
|
949
|
-
await this.injectFoundationDependencies(tasks, deps, warnings);
|
|
1145
|
+
await this.injectFoundationDependencies(tasks, deps, warnings, applyChanges);
|
|
950
1146
|
}
|
|
951
1147
|
let { nodes, dependents, missingRefs } = this.buildNodes(tasks, deps);
|
|
952
1148
|
const enableAgentRanking = Boolean(request.agentName);
|
|
@@ -954,8 +1150,9 @@ export class TaskOrderingService {
|
|
|
954
1150
|
const useAgent = enableAgentRanking || enableInference;
|
|
955
1151
|
const agentStream = request.agentStream !== false;
|
|
956
1152
|
let docContext;
|
|
957
|
-
if (useAgent) {
|
|
1153
|
+
if (useAgent || enrichMetadata) {
|
|
958
1154
|
docContext = await this.buildDocContext(project.key, warnings);
|
|
1155
|
+
this.enforcePlanningContextPolicy(planningContextPolicy, docContext);
|
|
959
1156
|
if (docContext && commandRun && this.recordTelemetry) {
|
|
960
1157
|
const contextTokens = estimateTokens(docContext.content);
|
|
961
1158
|
await this.jobService.recordTokenUsage({
|
|
@@ -991,7 +1188,7 @@ export class TaskOrderingService {
|
|
|
991
1188
|
stream: agentStream,
|
|
992
1189
|
warnings,
|
|
993
1190
|
});
|
|
994
|
-
await this.applyInferredDependencies(tasks, deps, inferred, warnings);
|
|
1191
|
+
await this.applyInferredDependencies(tasks, deps, inferred, warnings, applyChanges);
|
|
995
1192
|
({ nodes, dependents, missingRefs } = this.buildNodes(tasks, deps));
|
|
996
1193
|
}
|
|
997
1194
|
catch (error) {
|
|
@@ -1008,6 +1205,9 @@ export class TaskOrderingService {
|
|
|
1008
1205
|
if (missingContext.size > 0) {
|
|
1009
1206
|
warnings.push(`Tasks with open missing_context comments: ${Array.from(missingContext).length}`);
|
|
1010
1207
|
}
|
|
1208
|
+
if (enrichMetadata && !docContext) {
|
|
1209
|
+
warnings.push("Planning context unavailable: ordering metadata enrichment used task/dependency heuristics only.");
|
|
1210
|
+
}
|
|
1011
1211
|
const stageOrder = (request.stageOrder && request.stageOrder.length > 0
|
|
1012
1212
|
? request.stageOrder
|
|
1013
1213
|
: DEFAULT_STAGE_ORDER);
|
|
@@ -1021,7 +1221,10 @@ export class TaskOrderingService {
|
|
|
1021
1221
|
DEFAULT_STAGE_ORDER.forEach((stage, idx) => stageOrderMap.set(stage, idx));
|
|
1022
1222
|
}
|
|
1023
1223
|
const impact = this.dependencyImpactMap(dependents);
|
|
1024
|
-
const {
|
|
1224
|
+
const { metadataByTask, complexityByTask } = enrichMetadata
|
|
1225
|
+
? this.buildOrderingMetadata(nodes, impact, missingContext, docContext)
|
|
1226
|
+
: { metadataByTask: new Map(), complexityByTask: new Map() };
|
|
1227
|
+
const { ordered: initialOrder, cycle, cycleMembers } = this.topologicalSort(nodes, dependents, impact, complexityByTask, missingContext, undefined, stageOrderMap);
|
|
1025
1228
|
if (cycle) {
|
|
1026
1229
|
warnings.push("Dependency cycle detected; ordering may be partial.");
|
|
1027
1230
|
}
|
|
@@ -1102,12 +1305,20 @@ export class TaskOrderingService {
|
|
|
1102
1305
|
else if (enableAgentRanking && !resolvedAgent) {
|
|
1103
1306
|
warnings.push("Agent refinement skipped: no agent resolved.");
|
|
1104
1307
|
}
|
|
1105
|
-
const { ordered, cycle: cycleAfterAgent, cycleMembers: agentCycleMembers } = this.topologicalSort(nodes, dependents, impact, agentRank, stageOrderMap);
|
|
1308
|
+
const { ordered, cycle: cycleAfterAgent, cycleMembers: agentCycleMembers } = this.topologicalSort(nodes, dependents, impact, complexityByTask, missingContext, agentRank, stageOrderMap);
|
|
1106
1309
|
const finalCycleMembers = new Set([...cycleMembers, ...agentCycleMembers]);
|
|
1107
1310
|
if (cycleAfterAgent && !cycle) {
|
|
1108
1311
|
warnings.push("Agent-influenced ordering encountered a cycle; used partial order.");
|
|
1109
1312
|
}
|
|
1110
1313
|
const prioritized = ordered;
|
|
1314
|
+
if (enrichMetadata) {
|
|
1315
|
+
for (const task of prioritized) {
|
|
1316
|
+
const metadata = metadataByTask.get(task.id);
|
|
1317
|
+
if (metadata) {
|
|
1318
|
+
task.metadata = metadata;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1111
1322
|
const epicMap = new Map();
|
|
1112
1323
|
const storyMap = new Map();
|
|
1113
1324
|
prioritized.forEach((task, idx) => {
|
|
@@ -1119,7 +1330,12 @@ export class TaskOrderingService {
|
|
|
1119
1330
|
storyTasks.push(task);
|
|
1120
1331
|
storyMap.set(task.story_id, storyTasks);
|
|
1121
1332
|
});
|
|
1122
|
-
|
|
1333
|
+
if (applyChanges) {
|
|
1334
|
+
await this.persistPriorities(prioritized, epicMap, storyMap, enrichMetadata ? metadataByTask : undefined);
|
|
1335
|
+
}
|
|
1336
|
+
else {
|
|
1337
|
+
warnings.push("Dry run: priorities and dependency inferences were not persisted.");
|
|
1338
|
+
}
|
|
1123
1339
|
const mapped = this.mapResult(prioritized, impact, finalCycleMembers);
|
|
1124
1340
|
if (job) {
|
|
1125
1341
|
await this.jobService.updateJobStatus(job.id, "completed", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsService.d.ts","sourceRoot":"","sources":["../../../src/services/docs/DocsService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,YAAY,EAAkB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"DocsService.d.ts","sourceRoot":"","sources":["../../../src/services/docs/DocsService.ts"],"names":[],"mappings":"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"}
|