@mcoda/core 0.1.9 → 0.1.12

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.
Files changed (204) hide show
  1. package/README.md +2 -2
  2. package/dist/api/AgentsApi.d.ts +1 -0
  3. package/dist/api/AgentsApi.d.ts.map +1 -1
  4. package/dist/api/AgentsApi.js +136 -11
  5. package/dist/api/QaTasksApi.d.ts.map +1 -1
  6. package/dist/api/QaTasksApi.js +4 -0
  7. package/dist/prompts/PdrPrompts.d.ts.map +1 -1
  8. package/dist/prompts/PdrPrompts.js +6 -0
  9. package/dist/prompts/SdsPrompts.d.ts.map +1 -1
  10. package/dist/prompts/SdsPrompts.js +7 -0
  11. package/dist/services/agents/AgentRatingService.d.ts +19 -0
  12. package/dist/services/agents/AgentRatingService.d.ts.map +1 -1
  13. package/dist/services/agents/AgentRatingService.js +66 -2
  14. package/dist/services/agents/GatewayAgentService.d.ts +8 -0
  15. package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
  16. package/dist/services/agents/GatewayAgentService.js +462 -65
  17. package/dist/services/agents/GatewayHandoff.d.ts +5 -1
  18. package/dist/services/agents/GatewayHandoff.d.ts.map +1 -1
  19. package/dist/services/agents/GatewayHandoff.js +65 -32
  20. package/dist/services/agents/RoutingService.d.ts +1 -0
  21. package/dist/services/agents/RoutingService.d.ts.map +1 -1
  22. package/dist/services/agents/RoutingService.js +4 -4
  23. package/dist/services/backlog/BacklogService.d.ts +23 -0
  24. package/dist/services/backlog/BacklogService.d.ts.map +1 -1
  25. package/dist/services/backlog/BacklogService.js +62 -7
  26. package/dist/services/backlog/TaskOrderingHeuristics.d.ts +12 -0
  27. package/dist/services/backlog/TaskOrderingHeuristics.d.ts.map +1 -0
  28. package/dist/services/backlog/TaskOrderingHeuristics.js +56 -0
  29. package/dist/services/backlog/TaskOrderingService.d.ts +16 -4
  30. package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
  31. package/dist/services/backlog/TaskOrderingService.js +529 -73
  32. package/dist/services/docs/DocInventory.d.ts +11 -0
  33. package/dist/services/docs/DocInventory.d.ts.map +1 -0
  34. package/dist/services/docs/DocInventory.js +230 -0
  35. package/dist/services/docs/DocgenRunContext.d.ts +59 -0
  36. package/dist/services/docs/DocgenRunContext.d.ts.map +1 -0
  37. package/dist/services/docs/DocgenRunContext.js +4 -0
  38. package/dist/services/docs/DocsService.d.ts +59 -2
  39. package/dist/services/docs/DocsService.d.ts.map +1 -1
  40. package/dist/services/docs/DocsService.js +1701 -48
  41. package/dist/services/docs/alignment/DocAlignmentGraph.d.ts +23 -0
  42. package/dist/services/docs/alignment/DocAlignmentGraph.d.ts.map +1 -0
  43. package/dist/services/docs/alignment/DocAlignmentGraph.js +78 -0
  44. package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts +19 -0
  45. package/dist/services/docs/alignment/DocAlignmentPatcher.d.ts.map +1 -0
  46. package/dist/services/docs/alignment/DocAlignmentPatcher.js +222 -0
  47. package/dist/services/docs/patch/DocPatchEngine.d.ts +57 -0
  48. package/dist/services/docs/patch/DocPatchEngine.d.ts.map +1 -0
  49. package/dist/services/docs/patch/DocPatchEngine.js +331 -0
  50. package/dist/services/docs/review/Glossary.d.ts +16 -0
  51. package/dist/services/docs/review/Glossary.d.ts.map +1 -0
  52. package/dist/services/docs/review/Glossary.js +47 -0
  53. package/dist/services/docs/review/ReviewReportRenderer.d.ts +3 -0
  54. package/dist/services/docs/review/ReviewReportRenderer.d.ts.map +1 -0
  55. package/dist/services/docs/review/ReviewReportRenderer.js +133 -0
  56. package/dist/services/docs/review/ReviewReportSchema.d.ts +39 -0
  57. package/dist/services/docs/review/ReviewReportSchema.d.ts.map +1 -0
  58. package/dist/services/docs/review/ReviewReportSchema.js +47 -0
  59. package/dist/services/docs/review/ReviewTypes.d.ts +76 -0
  60. package/dist/services/docs/review/ReviewTypes.d.ts.map +1 -0
  61. package/dist/services/docs/review/ReviewTypes.js +94 -0
  62. package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts +7 -0
  63. package/dist/services/docs/review/gates/AdminOpenApiSpecGate.d.ts.map +1 -0
  64. package/dist/services/docs/review/gates/AdminOpenApiSpecGate.js +93 -0
  65. package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts +7 -0
  66. package/dist/services/docs/review/gates/ApiPathConsistencyGate.d.ts.map +1 -0
  67. package/dist/services/docs/review/gates/ApiPathConsistencyGate.js +308 -0
  68. package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts +8 -0
  69. package/dist/services/docs/review/gates/BuildReadyCompletenessGate.d.ts.map +1 -0
  70. package/dist/services/docs/review/gates/BuildReadyCompletenessGate.js +278 -0
  71. package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts +8 -0
  72. package/dist/services/docs/review/gates/DeploymentBlueprintGate.d.ts.map +1 -0
  73. package/dist/services/docs/review/gates/DeploymentBlueprintGate.js +487 -0
  74. package/dist/services/docs/review/gates/NoMaybesGate.d.ts +8 -0
  75. package/dist/services/docs/review/gates/NoMaybesGate.d.ts.map +1 -0
  76. package/dist/services/docs/review/gates/NoMaybesGate.js +145 -0
  77. package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts +7 -0
  78. package/dist/services/docs/review/gates/OpenApiCoverageGate.d.ts.map +1 -0
  79. package/dist/services/docs/review/gates/OpenApiCoverageGate.js +266 -0
  80. package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts +7 -0
  81. package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.d.ts.map +1 -0
  82. package/dist/services/docs/review/gates/OpenApiSchemaSanityGate.js +59 -0
  83. package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts +7 -0
  84. package/dist/services/docs/review/gates/OpenQuestionsGate.d.ts.map +1 -0
  85. package/dist/services/docs/review/gates/OpenQuestionsGate.js +200 -0
  86. package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts +7 -0
  87. package/dist/services/docs/review/gates/PdrInterfacesGate.d.ts.map +1 -0
  88. package/dist/services/docs/review/gates/PdrInterfacesGate.js +159 -0
  89. package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts +8 -0
  90. package/dist/services/docs/review/gates/PdrOpenQuestionsGate.d.ts.map +1 -0
  91. package/dist/services/docs/review/gates/PdrOpenQuestionsGate.js +129 -0
  92. package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts +7 -0
  93. package/dist/services/docs/review/gates/PdrOwnershipGate.d.ts.map +1 -0
  94. package/dist/services/docs/review/gates/PdrOwnershipGate.js +169 -0
  95. package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts +10 -0
  96. package/dist/services/docs/review/gates/PlaceholderArtifactGate.d.ts.map +1 -0
  97. package/dist/services/docs/review/gates/PlaceholderArtifactGate.js +261 -0
  98. package/dist/services/docs/review/gates/RfpConsentGate.d.ts +6 -0
  99. package/dist/services/docs/review/gates/RfpConsentGate.d.ts.map +1 -0
  100. package/dist/services/docs/review/gates/RfpConsentGate.js +127 -0
  101. package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts +7 -0
  102. package/dist/services/docs/review/gates/RfpDefinitionGate.d.ts.map +1 -0
  103. package/dist/services/docs/review/gates/RfpDefinitionGate.js +173 -0
  104. package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts +7 -0
  105. package/dist/services/docs/review/gates/SdsAdaptersGate.d.ts.map +1 -0
  106. package/dist/services/docs/review/gates/SdsAdaptersGate.js +196 -0
  107. package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts +7 -0
  108. package/dist/services/docs/review/gates/SdsDecisionsGate.d.ts.map +1 -0
  109. package/dist/services/docs/review/gates/SdsDecisionsGate.js +89 -0
  110. package/dist/services/docs/review/gates/SdsOpsGate.d.ts +7 -0
  111. package/dist/services/docs/review/gates/SdsOpsGate.d.ts.map +1 -0
  112. package/dist/services/docs/review/gates/SdsOpsGate.js +162 -0
  113. package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts +7 -0
  114. package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.d.ts.map +1 -0
  115. package/dist/services/docs/review/gates/SdsPolicyTelemetryGate.js +166 -0
  116. package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts +7 -0
  117. package/dist/services/docs/review/gates/SqlRequiredTablesGate.d.ts.map +1 -0
  118. package/dist/services/docs/review/gates/SqlRequiredTablesGate.js +273 -0
  119. package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts +7 -0
  120. package/dist/services/docs/review/gates/SqlSyntaxGate.d.ts.map +1 -0
  121. package/dist/services/docs/review/gates/SqlSyntaxGate.js +203 -0
  122. package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts +9 -0
  123. package/dist/services/docs/review/gates/TerminologyNormalizationGate.d.ts.map +1 -0
  124. package/dist/services/docs/review/gates/TerminologyNormalizationGate.js +217 -0
  125. package/dist/services/docs/review/glossary.json +47 -0
  126. package/dist/services/estimate/EstimateService.d.ts +2 -0
  127. package/dist/services/estimate/EstimateService.d.ts.map +1 -1
  128. package/dist/services/estimate/EstimateService.js +66 -18
  129. package/dist/services/estimate/VelocityService.d.ts +4 -0
  130. package/dist/services/estimate/VelocityService.d.ts.map +1 -1
  131. package/dist/services/estimate/VelocityService.js +179 -36
  132. package/dist/services/estimate/types.d.ts +1 -0
  133. package/dist/services/estimate/types.d.ts.map +1 -1
  134. package/dist/services/execution/GatewayTrioService.d.ts +71 -4
  135. package/dist/services/execution/GatewayTrioService.d.ts.map +1 -1
  136. package/dist/services/execution/GatewayTrioService.js +1695 -328
  137. package/dist/services/execution/QaApiRunner.d.ts +30 -0
  138. package/dist/services/execution/QaApiRunner.d.ts.map +1 -0
  139. package/dist/services/execution/QaApiRunner.js +881 -0
  140. package/dist/services/execution/QaFollowupService.d.ts +1 -0
  141. package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
  142. package/dist/services/execution/QaFollowupService.js +8 -2
  143. package/dist/services/execution/QaPlanValidator.d.ts +10 -0
  144. package/dist/services/execution/QaPlanValidator.d.ts.map +1 -0
  145. package/dist/services/execution/QaPlanValidator.js +128 -0
  146. package/dist/services/execution/QaProfileService.d.ts +21 -1
  147. package/dist/services/execution/QaProfileService.d.ts.map +1 -1
  148. package/dist/services/execution/QaProfileService.js +214 -29
  149. package/dist/services/execution/QaTasksService.d.ts +41 -1
  150. package/dist/services/execution/QaTasksService.d.ts.map +1 -1
  151. package/dist/services/execution/QaTasksService.js +2851 -500
  152. package/dist/services/execution/QaTestCommandBuilder.d.ts +51 -0
  153. package/dist/services/execution/QaTestCommandBuilder.d.ts.map +1 -0
  154. package/dist/services/execution/QaTestCommandBuilder.js +495 -0
  155. package/dist/services/execution/TaskSelectionService.d.ts +4 -2
  156. package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
  157. package/dist/services/execution/TaskSelectionService.js +144 -28
  158. package/dist/services/execution/TaskStateService.d.ts +19 -6
  159. package/dist/services/execution/TaskStateService.d.ts.map +1 -1
  160. package/dist/services/execution/TaskStateService.js +128 -13
  161. package/dist/services/execution/WorkOnTasksService.d.ts +19 -2
  162. package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
  163. package/dist/services/execution/WorkOnTasksService.js +3913 -1225
  164. package/dist/services/jobs/JobInsightsService.d.ts +4 -0
  165. package/dist/services/jobs/JobInsightsService.d.ts.map +1 -1
  166. package/dist/services/jobs/JobInsightsService.js +51 -5
  167. package/dist/services/jobs/JobResumeService.d.ts.map +1 -1
  168. package/dist/services/jobs/JobResumeService.js +23 -10
  169. package/dist/services/jobs/JobService.d.ts +56 -4
  170. package/dist/services/jobs/JobService.d.ts.map +1 -1
  171. package/dist/services/jobs/JobService.js +232 -1
  172. package/dist/services/openapi/OpenApiService.d.ts +41 -0
  173. package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
  174. package/dist/services/openapi/OpenApiService.js +889 -98
  175. package/dist/services/planning/CreateTasksService.d.ts +15 -0
  176. package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
  177. package/dist/services/planning/CreateTasksService.js +311 -6
  178. package/dist/services/planning/RefineTasksService.d.ts +4 -0
  179. package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
  180. package/dist/services/planning/RefineTasksService.js +225 -24
  181. package/dist/services/review/CodeReviewService.d.ts +4 -0
  182. package/dist/services/review/CodeReviewService.d.ts.map +1 -1
  183. package/dist/services/review/CodeReviewService.js +778 -232
  184. package/dist/services/review/ReviewNormalizer.d.ts +9 -0
  185. package/dist/services/review/ReviewNormalizer.d.ts.map +1 -0
  186. package/dist/services/review/ReviewNormalizer.js +147 -0
  187. package/dist/services/shared/AuthErrors.d.ts +3 -0
  188. package/dist/services/shared/AuthErrors.d.ts.map +1 -0
  189. package/dist/services/shared/AuthErrors.js +17 -0
  190. package/dist/services/shared/DocdexGuidance.d.ts +7 -0
  191. package/dist/services/shared/DocdexGuidance.d.ts.map +1 -0
  192. package/dist/services/shared/DocdexGuidance.js +12 -0
  193. package/dist/services/shared/ProjectGuidance.d.ts +12 -1
  194. package/dist/services/shared/ProjectGuidance.d.ts.map +1 -1
  195. package/dist/services/shared/ProjectGuidance.js +64 -7
  196. package/dist/services/system/ToolDenylist.d.ts +13 -0
  197. package/dist/services/system/ToolDenylist.d.ts.map +1 -0
  198. package/dist/services/system/ToolDenylist.js +85 -0
  199. package/dist/services/telemetry/TelemetryService.d.ts.map +1 -1
  200. package/dist/services/telemetry/TelemetryService.js +39 -7
  201. package/dist/workspace/WorkspaceManager.d.ts +22 -0
  202. package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
  203. package/dist/workspace/WorkspaceManager.js +203 -32
  204. package/package.json +6 -5
@@ -2,19 +2,21 @@ import fs from "node:fs/promises";
2
2
  import { AgentService } from "@mcoda/agents";
3
3
  import { DocdexClient } from "@mcoda/integrations";
4
4
  import { GlobalRepository, WorkspaceRepository, Connection } from "@mcoda/db";
5
- import { PathHelper } from "@mcoda/shared";
5
+ import { PathHelper, READY_TO_CODE_REVIEW, normalizeReviewStatuses } from "@mcoda/shared";
6
6
  import { JobService } from "../jobs/JobService.js";
7
7
  import { RoutingService } from "../agents/RoutingService.js";
8
- const DEFAULT_STATUSES = ["not_started", "in_progress", "blocked", "ready_to_review", "ready_to_qa"];
8
+ import { classifyTask } from "./TaskOrderingHeuristics.js";
9
+ const DEFAULT_STATUSES = ["not_started", "in_progress", "changes_requested", READY_TO_CODE_REVIEW, "ready_to_qa"];
9
10
  const DONE_STATUSES = new Set(["completed", "cancelled"]);
11
+ const DEFAULT_STAGE_ORDER = ["foundation", "backend", "frontend", "other"];
10
12
  const STATUS_RANK = {
11
13
  in_progress: 0,
14
+ changes_requested: 0,
12
15
  not_started: 1,
13
- ready_to_review: 2,
16
+ [READY_TO_CODE_REVIEW]: 2,
14
17
  ready_to_qa: 3,
15
- blocked: 4,
16
- completed: 5,
17
- cancelled: 6,
18
+ completed: 4,
19
+ cancelled: 5,
18
20
  };
19
21
  const hasTables = async (db, required) => {
20
22
  const placeholders = required.map(() => "?").join(", ");
@@ -24,7 +26,8 @@ const hasTables = async (db, required) => {
24
26
  const normalizeStatuses = (statuses) => {
25
27
  if (!statuses || statuses.length === 0)
26
28
  return DEFAULT_STATUSES;
27
- return Array.from(new Set(statuses.map((s) => s.toLowerCase().trim()).filter(Boolean)));
29
+ const normalized = Array.from(new Set(statuses.map((s) => s.toLowerCase().trim()).filter(Boolean))).filter((status) => status !== "blocked");
30
+ return normalizeReviewStatuses(normalized);
28
31
  };
29
32
  const estimateTokens = (text) => Math.max(1, Math.ceil((text ?? "").length / 4));
30
33
  const SDS_DEPENDENCY_GUIDE = [
@@ -32,8 +35,160 @@ const SDS_DEPENDENCY_GUIDE = [
32
35
  "- Enforce topological ordering: never place a task before any of its dependencies.",
33
36
  "- Prioritize tasks that unlock the most downstream work (direct + indirect dependents).",
34
37
  "- Tie-break by existing priority, then lower story points, then older tasks, then status (in_progress before not_started).",
35
- "- Blocked tasks should remain after unblocked tasks unless explicitly requested.",
36
38
  ].join("\n");
39
+ const extractJson = (raw) => {
40
+ if (!raw)
41
+ return undefined;
42
+ const fencedMatches = [...raw.matchAll(/```json([\s\S]*?)```/g)].map((match) => match[1]);
43
+ const stripped = raw.replace(/<think>[\s\S]*?<\/think>/g, "");
44
+ const candidates = [...fencedMatches, stripped, raw].filter((candidate) => candidate.trim().length > 0);
45
+ for (const candidate of candidates) {
46
+ const parsed = tryParseJson(candidate);
47
+ if (parsed !== undefined)
48
+ return parsed;
49
+ }
50
+ return undefined;
51
+ };
52
+ const tryParseJson = (value) => {
53
+ try {
54
+ return JSON.parse(value);
55
+ }
56
+ catch {
57
+ // continue
58
+ }
59
+ const blocks = extractJsonBlocks(value).reverse();
60
+ for (const block of blocks) {
61
+ try {
62
+ return JSON.parse(block);
63
+ }
64
+ catch {
65
+ // continue
66
+ }
67
+ }
68
+ return undefined;
69
+ };
70
+ const extractJsonBlocks = (value) => {
71
+ const results = [];
72
+ const stack = [];
73
+ let start = -1;
74
+ let inString = false;
75
+ let escaped = false;
76
+ for (let i = 0; i < value.length; i += 1) {
77
+ const ch = value[i];
78
+ if (inString) {
79
+ if (escaped) {
80
+ escaped = false;
81
+ continue;
82
+ }
83
+ if (ch === "\\") {
84
+ escaped = true;
85
+ continue;
86
+ }
87
+ if (ch === "\"")
88
+ inString = false;
89
+ continue;
90
+ }
91
+ if (ch === "\"") {
92
+ inString = true;
93
+ continue;
94
+ }
95
+ if (ch === "{") {
96
+ if (stack.length === 0)
97
+ start = i;
98
+ stack.push("}");
99
+ continue;
100
+ }
101
+ if (ch === "[") {
102
+ if (stack.length === 0)
103
+ start = i;
104
+ stack.push("]");
105
+ continue;
106
+ }
107
+ if (stack.length > 0 && ch === stack[stack.length - 1]) {
108
+ stack.pop();
109
+ if (stack.length === 0 && start >= 0) {
110
+ results.push(value.slice(start, i + 1));
111
+ start = -1;
112
+ }
113
+ }
114
+ }
115
+ return results;
116
+ };
117
+ export const parseDependencyInferenceOutput = (output, validTaskKeys, warnings) => {
118
+ const parsed = extractJson(output);
119
+ if (!parsed) {
120
+ warnings.push("Agent dependency inference output could not be parsed; skipping.");
121
+ return [];
122
+ }
123
+ const dependencyEntries = Array.isArray(parsed)
124
+ ? parsed
125
+ : Array.isArray(parsed?.dependencies)
126
+ ? parsed.dependencies
127
+ : Array.isArray(parsed?.deps)
128
+ ? parsed.deps
129
+ : undefined;
130
+ if (!Array.isArray(dependencyEntries)) {
131
+ warnings.push("Agent dependency inference missing dependencies list; skipping.");
132
+ return [];
133
+ }
134
+ const dependenciesByTask = new Map();
135
+ let invalidTasks = 0;
136
+ let invalidDeps = 0;
137
+ let selfDeps = 0;
138
+ for (const entry of dependencyEntries) {
139
+ const taskKey = typeof entry?.task_key === "string"
140
+ ? entry.task_key
141
+ : typeof entry?.taskKey === "string"
142
+ ? entry.taskKey
143
+ : undefined;
144
+ if (!taskKey || !validTaskKeys.has(taskKey)) {
145
+ invalidTasks += 1;
146
+ continue;
147
+ }
148
+ const rawDepends = entry?.depends_on ?? entry?.dependsOn;
149
+ if (rawDepends === undefined) {
150
+ continue;
151
+ }
152
+ if (!Array.isArray(rawDepends)) {
153
+ invalidDeps += 1;
154
+ continue;
155
+ }
156
+ const dependsRaw = rawDepends;
157
+ const deps = dependenciesByTask.get(taskKey) ?? new Set();
158
+ for (const dep of dependsRaw) {
159
+ if (typeof dep !== "string") {
160
+ invalidDeps += 1;
161
+ continue;
162
+ }
163
+ if (dep === taskKey) {
164
+ selfDeps += 1;
165
+ continue;
166
+ }
167
+ if (!validTaskKeys.has(dep)) {
168
+ invalidDeps += 1;
169
+ continue;
170
+ }
171
+ deps.add(dep);
172
+ }
173
+ if (deps.size > 0) {
174
+ dependenciesByTask.set(taskKey, deps);
175
+ }
176
+ }
177
+ if (invalidTasks > 0) {
178
+ warnings.push(`Agent dependency inference ignored ${invalidTasks} invalid task keys.`);
179
+ }
180
+ if (invalidDeps > 0) {
181
+ warnings.push(`Agent dependency inference ignored ${invalidDeps} invalid dependency keys.`);
182
+ }
183
+ if (selfDeps > 0) {
184
+ warnings.push(`Agent dependency inference ignored ${selfDeps} self-dependencies.`);
185
+ }
186
+ const inferred = [];
187
+ for (const [taskKey, deps] of dependenciesByTask.entries()) {
188
+ inferred.push({ taskKey, dependsOnKeys: Array.from(deps) });
189
+ }
190
+ return inferred;
191
+ };
37
192
  export class TaskOrderingService {
38
193
  constructor(workspace, db, repo, jobService, agentService, globalRepo, routingService, docdex, recordTelemetry) {
39
194
  this.workspace = workspace;
@@ -65,9 +220,11 @@ export class TaskOrderingService {
65
220
  const globalRepo = await GlobalRepository.create();
66
221
  const agentService = new AgentService(globalRepo);
67
222
  const routingService = await RoutingService.create();
223
+ const docdexRepoId = workspace.config?.docdexRepoId ?? process.env.MCODA_DOCDEX_REPO_ID ?? process.env.DOCDEX_REPO_ID;
68
224
  const docdex = new DocdexClient({
69
225
  workspaceRoot: workspace.workspaceRoot,
70
226
  baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
227
+ repoId: docdexRepoId,
71
228
  });
72
229
  return new TaskOrderingService(workspace, connection.db, repo, jobService, agentService, globalRepo, routingService, docdex, options.recordTelemetry !== false);
73
230
  }
@@ -207,6 +364,19 @@ export class TaskOrderingService {
207
364
  }
208
365
  return grouped;
209
366
  }
367
+ async loadMissingContext(taskIds) {
368
+ if (!taskIds.length)
369
+ return new Set();
370
+ const placeholders = taskIds.map(() => "?").join(", ");
371
+ const rows = await this.db.all(`
372
+ SELECT DISTINCT task_id
373
+ FROM task_comments
374
+ WHERE task_id IN (${placeholders})
375
+ AND LOWER(category) = 'missing_context'
376
+ AND (status IS NULL OR LOWER(status) = 'open')
377
+ `, ...taskIds);
378
+ return new Set(rows.map((row) => row.task_id));
379
+ }
210
380
  dependencyImpactMap(dependents) {
211
381
  const memo = new Map();
212
382
  const visit = (taskId, stack) => {
@@ -238,7 +408,210 @@ export class TaskOrderingService {
238
408
  }
239
409
  return memo;
240
410
  }
241
- compareTasks(a, b, impact, agentRank) {
411
+ resolveClassification(task) {
412
+ const metadata = task.metadata ?? {};
413
+ const stage = typeof metadata.stage === "string" ? metadata.stage.toLowerCase() : undefined;
414
+ const foundation = typeof metadata.foundation === "boolean" ? metadata.foundation : undefined;
415
+ if (stage && ["foundation", "backend", "frontend", "other"].includes(stage)) {
416
+ return {
417
+ stage: stage,
418
+ foundation: foundation ?? stage === "foundation",
419
+ };
420
+ }
421
+ const inferred = classifyTask({ title: task.title, description: task.description, type: task.type ?? undefined });
422
+ return { stage: inferred.stage, foundation: inferred.foundation };
423
+ }
424
+ buildDependencyGraph(tasks, deps) {
425
+ const taskIds = new Set(tasks.map((task) => task.id));
426
+ const graph = new Map();
427
+ for (const task of tasks) {
428
+ const rows = deps.get(task.id) ?? [];
429
+ const edges = new Set();
430
+ for (const dep of rows) {
431
+ if (!dep.depends_on_task_id)
432
+ continue;
433
+ if (!taskIds.has(dep.depends_on_task_id))
434
+ continue;
435
+ edges.add(dep.depends_on_task_id);
436
+ }
437
+ if (edges.size > 0) {
438
+ graph.set(task.id, edges);
439
+ }
440
+ }
441
+ return graph;
442
+ }
443
+ hasDependencyPath(graph, fromId, toId) {
444
+ if (fromId === toId)
445
+ return true;
446
+ const visited = new Set();
447
+ const stack = [fromId];
448
+ while (stack.length > 0) {
449
+ const current = stack.pop();
450
+ if (current === toId)
451
+ return true;
452
+ if (visited.has(current))
453
+ continue;
454
+ visited.add(current);
455
+ const neighbors = graph.get(current);
456
+ if (!neighbors)
457
+ continue;
458
+ for (const next of neighbors) {
459
+ if (!visited.has(next))
460
+ stack.push(next);
461
+ }
462
+ }
463
+ return false;
464
+ }
465
+ async injectFoundationDependencies(tasks, deps, warnings) {
466
+ const classification = new Map();
467
+ for (const task of tasks) {
468
+ classification.set(task.id, this.resolveClassification(task));
469
+ }
470
+ const foundationTasks = tasks.filter((task) => classification.get(task.id)?.foundation);
471
+ const nonFoundationTasks = tasks.filter((task) => !classification.get(task.id)?.foundation);
472
+ if (foundationTasks.length === 0 || nonFoundationTasks.length === 0)
473
+ return;
474
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
475
+ const dependencyGraph = this.buildDependencyGraph(tasks, deps);
476
+ const inserts = [];
477
+ let skippedCycles = 0;
478
+ const skippedEdges = [];
479
+ for (const task of nonFoundationTasks) {
480
+ const existing = new Set((deps.get(task.id) ?? [])
481
+ .map((dep) => dep.depends_on_task_id ?? "")
482
+ .filter(Boolean));
483
+ for (const foundation of foundationTasks) {
484
+ if (task.id === foundation.id)
485
+ continue;
486
+ if (existing.has(foundation.id))
487
+ continue;
488
+ if (this.hasDependencyPath(dependencyGraph, foundation.id, task.id)) {
489
+ skippedCycles += 1;
490
+ if (skippedEdges.length < 5) {
491
+ skippedEdges.push(`${task.key}->${foundation.key}`);
492
+ }
493
+ continue;
494
+ }
495
+ inserts.push({
496
+ taskId: task.id,
497
+ dependsOnTaskId: foundation.id,
498
+ relationType: "inferred_foundation",
499
+ });
500
+ existing.add(foundation.id);
501
+ const edges = dependencyGraph.get(task.id) ?? new Set();
502
+ edges.add(foundation.id);
503
+ dependencyGraph.set(task.id, edges);
504
+ }
505
+ }
506
+ if (inserts.length === 0) {
507
+ if (skippedCycles > 0) {
508
+ warnings.push(`Skipped ${skippedCycles} inferred foundation deps due to cycles.`);
509
+ if (skippedEdges.length > 0) {
510
+ warnings.push(`Skipped inferred foundation deps (cycle sample): ${skippedEdges.join(", ")}`);
511
+ }
512
+ }
513
+ return;
514
+ }
515
+ await this.repo.insertTaskDependencies(inserts, true);
516
+ for (const insert of inserts) {
517
+ const depList = deps.get(insert.taskId) ?? [];
518
+ const dependsOn = taskById.get(insert.dependsOnTaskId);
519
+ depList.push({
520
+ task_id: insert.taskId,
521
+ depends_on_task_id: insert.dependsOnTaskId,
522
+ depends_on_key: dependsOn?.key,
523
+ depends_on_status: dependsOn?.status,
524
+ });
525
+ deps.set(insert.taskId, depList);
526
+ }
527
+ warnings.push(`Injected ${inserts.length} inferred foundation deps.`);
528
+ if (skippedCycles > 0) {
529
+ warnings.push(`Skipped ${skippedCycles} inferred foundation deps due to cycles.`);
530
+ if (skippedEdges.length > 0) {
531
+ warnings.push(`Skipped inferred foundation deps (cycle sample): ${skippedEdges.join(", ")}`);
532
+ }
533
+ }
534
+ }
535
+ async applyInferredDependencies(tasks, deps, inferred, warnings) {
536
+ if (inferred.length === 0)
537
+ return;
538
+ const taskByKey = new Map(tasks.map((task) => [task.key, task]));
539
+ const dependencyGraph = this.buildDependencyGraph(tasks, deps);
540
+ const inserts = [];
541
+ let skippedCycles = 0;
542
+ const skippedEdges = [];
543
+ for (const entry of inferred) {
544
+ const task = taskByKey.get(entry.taskKey);
545
+ if (!task)
546
+ continue;
547
+ const existing = new Set((deps.get(task.id) ?? [])
548
+ .map((dep) => dep.depends_on_task_id ?? "")
549
+ .filter(Boolean));
550
+ for (const depKey of entry.dependsOnKeys) {
551
+ const dependsOn = taskByKey.get(depKey);
552
+ if (!dependsOn)
553
+ continue;
554
+ if (dependsOn.id === task.id)
555
+ continue;
556
+ if (existing.has(dependsOn.id))
557
+ continue;
558
+ if (this.hasDependencyPath(dependencyGraph, dependsOn.id, task.id)) {
559
+ skippedCycles += 1;
560
+ if (skippedEdges.length < 5) {
561
+ skippedEdges.push(`${task.key}->${dependsOn.key}`);
562
+ }
563
+ continue;
564
+ }
565
+ inserts.push({
566
+ taskId: task.id,
567
+ dependsOnTaskId: dependsOn.id,
568
+ relationType: "inferred_agent",
569
+ });
570
+ existing.add(dependsOn.id);
571
+ const edges = dependencyGraph.get(task.id) ?? new Set();
572
+ edges.add(dependsOn.id);
573
+ dependencyGraph.set(task.id, edges);
574
+ }
575
+ }
576
+ if (inserts.length === 0) {
577
+ if (skippedCycles > 0) {
578
+ warnings.push(`Skipped ${skippedCycles} inferred agent deps due to cycles.`);
579
+ if (skippedEdges.length > 0) {
580
+ warnings.push(`Skipped inferred agent deps (cycle sample): ${skippedEdges.join(", ")}`);
581
+ }
582
+ }
583
+ return;
584
+ }
585
+ await this.repo.insertTaskDependencies(inserts, true);
586
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
587
+ for (const insert of inserts) {
588
+ const depList = deps.get(insert.taskId) ?? [];
589
+ const dependsOn = taskById.get(insert.dependsOnTaskId);
590
+ depList.push({
591
+ task_id: insert.taskId,
592
+ depends_on_task_id: insert.dependsOnTaskId,
593
+ depends_on_key: dependsOn?.key,
594
+ depends_on_status: dependsOn?.status,
595
+ });
596
+ deps.set(insert.taskId, depList);
597
+ }
598
+ warnings.push(`Applied ${inserts.length} inferred agent deps.`);
599
+ if (skippedCycles > 0) {
600
+ warnings.push(`Skipped ${skippedCycles} inferred agent deps due to cycles.`);
601
+ if (skippedEdges.length > 0) {
602
+ warnings.push(`Skipped inferred agent deps (cycle sample): ${skippedEdges.join(", ")}`);
603
+ }
604
+ }
605
+ }
606
+ compareTasks(a, b, impact, agentRank, stageOrderMap) {
607
+ const priorityA = a.priority ?? Number.MAX_SAFE_INTEGER;
608
+ const priorityB = b.priority ?? Number.MAX_SAFE_INTEGER;
609
+ if (priorityA !== priorityB)
610
+ return priorityA - priorityB;
611
+ const impactA = impact.get(a.id)?.total ?? 0;
612
+ const impactB = impact.get(b.id)?.total ?? 0;
613
+ if (impactA !== impactB)
614
+ return impactB - impactA;
242
615
  const rankA = agentRank?.get(a.id);
243
616
  const rankB = agentRank?.get(b.id);
244
617
  if (rankA !== undefined || rankB !== undefined) {
@@ -249,14 +622,17 @@ export class TaskOrderingService {
249
622
  if (rankA !== rankB)
250
623
  return rankA - rankB;
251
624
  }
252
- const impactA = impact.get(a.id)?.total ?? 0;
253
- const impactB = impact.get(b.id)?.total ?? 0;
254
- if (impactA !== impactB)
255
- return impactB - impactA;
256
- const priorityA = a.priority ?? Number.MAX_SAFE_INTEGER;
257
- const priorityB = b.priority ?? Number.MAX_SAFE_INTEGER;
258
- if (priorityA !== priorityB)
259
- return priorityA - priorityB;
625
+ const classA = this.resolveClassification(a);
626
+ const classB = this.resolveClassification(b);
627
+ if (classA.foundation !== classB.foundation) {
628
+ return classA.foundation ? -1 : 1;
629
+ }
630
+ if (stageOrderMap) {
631
+ const stageA = stageOrderMap.get(classA.stage) ?? stageOrderMap.get("other") ?? Number.MAX_SAFE_INTEGER;
632
+ const stageB = stageOrderMap.get(classB.stage) ?? stageOrderMap.get("other") ?? Number.MAX_SAFE_INTEGER;
633
+ if (stageA !== stageB)
634
+ return stageA - stageB;
635
+ }
260
636
  const spA = a.story_points ?? Number.POSITIVE_INFINITY;
261
637
  const spB = b.story_points ?? Number.POSITIVE_INFINITY;
262
638
  if (spA !== spB)
@@ -271,7 +647,7 @@ export class TaskOrderingService {
271
647
  return statusA - statusB;
272
648
  return a.key.localeCompare(b.key);
273
649
  }
274
- topologicalSort(tasks, edges, impact, agentRank) {
650
+ topologicalSort(tasks, edges, impact, agentRank, stageOrderMap) {
275
651
  const indegree = new Map();
276
652
  const taskMap = new Map(tasks.map((t) => [t.id, t]));
277
653
  for (const task of tasks) {
@@ -285,7 +661,7 @@ export class TaskOrderingService {
285
661
  }
286
662
  }
287
663
  const queue = tasks.filter((t) => (indegree.get(t.id) ?? 0) === 0);
288
- const sortQueue = () => queue.sort((a, b) => this.compareTasks(a, b, impact, agentRank));
664
+ const sortQueue = () => queue.sort((a, b) => this.compareTasks(a, b, impact, agentRank, stageOrderMap));
289
665
  sortQueue();
290
666
  const ordered = [];
291
667
  const visited = new Set();
@@ -313,7 +689,7 @@ export class TaskOrderingService {
313
689
  }
314
690
  }
315
691
  const remaining = tasks.filter((t) => !visited.has(t.id));
316
- remaining.sort((a, b) => this.compareTasks(a, b, impact, agentRank));
692
+ remaining.sort((a, b) => this.compareTasks(a, b, impact, agentRank, stageOrderMap));
317
693
  ordered.push(...remaining);
318
694
  }
319
695
  return { ordered, cycle, cycleMembers };
@@ -324,29 +700,23 @@ export class TaskOrderingService {
324
700
  const missingRefs = new Set();
325
701
  const nodes = tasks.map((task) => {
326
702
  const taskDeps = deps.get(task.id) ?? [];
327
- const blockedBy = [];
328
703
  const missing = [];
329
704
  for (const dep of taskDeps) {
330
705
  const status = dep.depends_on_status?.toLowerCase();
331
706
  if (!dep.depends_on_task_id) {
332
707
  missing.push(dep.depends_on_key ?? "unknown");
333
708
  missingRefs.add(dep.depends_on_key ?? "unknown");
334
- blockedBy.push(dep.depends_on_key ?? "unknown");
335
709
  continue;
336
710
  }
337
711
  const inScope = taskIds.has(dep.depends_on_task_id);
338
712
  const isDone = DONE_STATUSES.has(status ?? "");
339
713
  if (!inScope) {
340
714
  if (!isDone) {
341
- blockedBy.push(dep.depends_on_key ?? dep.depends_on_task_id);
342
715
  missing.push(dep.depends_on_key ?? dep.depends_on_task_id);
343
716
  missingRefs.add(dep.depends_on_key ?? dep.depends_on_task_id);
344
717
  }
345
718
  continue;
346
719
  }
347
- if (!isDone) {
348
- blockedBy.push(dep.depends_on_key ?? dep.depends_on_task_id);
349
- }
350
720
  const list = dependents.get(dep.depends_on_task_id) ?? [];
351
721
  list.push(task.id);
352
722
  dependents.set(dep.depends_on_task_id, list);
@@ -354,7 +724,6 @@ export class TaskOrderingService {
354
724
  return {
355
725
  ...task,
356
726
  dependencies: taskDeps,
357
- blockedBy,
358
727
  missingDependencies: missing,
359
728
  };
360
729
  });
@@ -388,12 +757,20 @@ export class TaskOrderingService {
388
757
  return { output: result.output, adapter: result.adapter };
389
758
  }
390
759
  applyAgentRanking(ordered, agentOutput, warnings) {
760
+ const parsed = extractJson(agentOutput);
761
+ if (!parsed) {
762
+ warnings.push("Agent output could not be parsed; using dependency-only ordering.");
763
+ return undefined;
764
+ }
765
+ const order = Array.isArray(parsed) ? parsed : parsed.order;
766
+ if (!Array.isArray(order)) {
767
+ warnings.push("Agent output missing order list; using dependency-only ordering.");
768
+ return undefined;
769
+ }
391
770
  try {
392
- const parsed = JSON.parse(agentOutput);
393
- const order = parsed.order ?? [];
394
771
  const ranking = new Map();
395
772
  order.forEach((entry, idx) => {
396
- const key = entry.task_key ?? entry.key;
773
+ const key = typeof entry === "string" ? entry : entry.task_key ?? entry.taskKey ?? entry.key;
397
774
  if (typeof key === "string") {
398
775
  ranking.set(key, idx);
399
776
  }
@@ -414,6 +791,45 @@ export class TaskOrderingService {
414
791
  return undefined;
415
792
  }
416
793
  }
794
+ async inferDependenciesWithAgent(agent, tasks, context) {
795
+ const summary = {
796
+ project: context.project.key,
797
+ epic: context.epic?.key,
798
+ story: context.story?.key,
799
+ tasks: tasks.map((task) => ({
800
+ task_key: task.key,
801
+ epic_key: task.epic_key,
802
+ story_key: task.story_key,
803
+ title: task.title,
804
+ description: task.description,
805
+ type: task.type,
806
+ depends_on: (task.dependencies ?? [])
807
+ .map((dep) => dep.depends_on_key ?? dep.depends_on_task_id)
808
+ .filter(Boolean),
809
+ })),
810
+ };
811
+ const prompt = [
812
+ "You are inferring dependencies across epics, stories, and tasks.",
813
+ "Return ONLY JSON matching:",
814
+ `{"dependencies":[{"task_key":"<key>","depends_on":["<key>"]}]}`,
815
+ "Only include task_key values from the input.",
816
+ "Do not add self-dependencies. Omit empty depends_on arrays.",
817
+ context.docContext ? `Doc context:\n${context.docContext.content}` : undefined,
818
+ "Task summary:",
819
+ JSON.stringify(summary, null, 2),
820
+ ]
821
+ .filter(Boolean)
822
+ .join("\n\n");
823
+ const { output } = await this.invokeAgent(agent, prompt, context.stream, {
824
+ command: "order-tasks",
825
+ phase: "infer_dependencies",
826
+ project: context.project.key,
827
+ epic: context.epic?.key,
828
+ story: context.story?.key,
829
+ });
830
+ const taskKeys = new Set(tasks.map((task) => task.key));
831
+ return parseDependencyInferenceOutput(output, taskKeys, context.warnings);
832
+ }
417
833
  async persistPriorities(ordered, epicMap, storyMap) {
418
834
  await this.repo.withTransaction(async () => {
419
835
  for (let i = 0; i < ordered.length; i += 1) {
@@ -440,7 +856,7 @@ export class TaskOrderingService {
440
856
  }
441
857
  });
442
858
  }
443
- mapResult(ordered, blockedSet, impact, cycleMembers) {
859
+ mapResult(ordered, impact, cycleMembers) {
444
860
  const result = ordered.map((task, idx) => ({
445
861
  taskId: task.id,
446
862
  taskKey: task.key,
@@ -453,15 +869,12 @@ export class TaskOrderingService {
453
869
  storyId: task.story_id,
454
870
  storyKey: task.story_key,
455
871
  storyTitle: task.story_title,
456
- blocked: blockedSet.has(task.id),
457
- blockedBy: task.blockedBy,
458
872
  dependencyKeys: (task.dependencies ?? []).map((d) => d.depends_on_key ?? d.depends_on_task_id ?? "").filter(Boolean),
459
873
  dependencyImpact: impact.get(task.id) ?? { direct: 0, total: 0 },
460
874
  cycleDetected: cycleMembers.has(task.id) || undefined,
461
875
  metadata: task.metadata,
462
876
  }));
463
- const blocked = result.filter((t) => t.blocked);
464
- return { ordered: result, blocked };
877
+ return { ordered: result };
465
878
  }
466
879
  async orderTasks(request) {
467
880
  if (!request.projectKey) {
@@ -469,6 +882,9 @@ export class TaskOrderingService {
469
882
  }
470
883
  const statuses = normalizeStatuses(request.statusFilter);
471
884
  const warnings = [];
885
+ if (request.statusFilter?.some((status) => status.toLowerCase().trim() === "blocked")) {
886
+ warnings.push("Status 'blocked' is no longer supported; ignoring it in order-tasks.");
887
+ }
472
888
  const commandRun = this.recordTelemetry
473
889
  ? await this.jobService.startCommandRun("order-tasks", request.projectKey, {
474
890
  taskIds: undefined,
@@ -486,7 +902,6 @@ export class TaskOrderingService {
486
902
  storyKey: request.storyKey,
487
903
  assignee: request.assignee,
488
904
  statuses,
489
- includeBlocked: request.includeBlocked === true,
490
905
  agent: request.agentName,
491
906
  },
492
907
  })
@@ -506,25 +921,16 @@ export class TaskOrderingService {
506
921
  }
507
922
  const tasks = await this.fetchTasks(project.id, epic?.id, statuses, story?.id, request.assignee);
508
923
  const deps = await this.fetchDependencies(tasks.map((t) => t.id));
509
- const { nodes, dependents, missingRefs } = this.buildNodes(tasks, deps);
510
- if (missingRefs.size > 0) {
511
- warnings.push(`Missing dependencies referenced: ${Array.from(missingRefs).join(", ")}`);
924
+ if (request.injectFoundationDeps !== false) {
925
+ await this.injectFoundationDependencies(tasks, deps, warnings);
512
926
  }
513
- const blockedSet = new Set();
514
- for (const node of nodes) {
515
- if (node.blockedBy.length > 0 || node.status.toLowerCase() === "blocked") {
516
- blockedSet.add(node.id);
517
- }
518
- }
519
- const impact = this.dependencyImpactMap(dependents);
520
- const { ordered: initialOrder, cycle, cycleMembers } = this.topologicalSort(nodes, dependents, impact);
521
- if (cycle) {
522
- warnings.push("Dependency cycle detected; ordering may be partial.");
523
- }
524
- let agentRank;
525
- const enableAgent = Boolean(request.agentName);
927
+ let { nodes, dependents, missingRefs } = this.buildNodes(tasks, deps);
928
+ const enableAgentRanking = Boolean(request.agentName);
929
+ const enableInference = request.inferDependencies === true;
930
+ const useAgent = enableAgentRanking || enableInference;
931
+ const agentStream = request.agentStream !== false;
526
932
  let docContext;
527
- if (enableAgent) {
933
+ if (useAgent) {
528
934
  docContext = await this.buildDocContext(project.key, warnings);
529
935
  if (docContext && commandRun && this.recordTelemetry) {
530
936
  const contextTokens = estimateTokens(docContext.content);
@@ -542,9 +948,62 @@ export class TaskOrderingService {
542
948
  });
543
949
  }
544
950
  }
545
- if (enableAgent) {
951
+ let resolvedAgent;
952
+ if (useAgent) {
953
+ try {
954
+ resolvedAgent = await this.resolveAgent(request.agentName);
955
+ }
956
+ catch (error) {
957
+ warnings.push(`Agent resolution failed: ${error.message}`);
958
+ }
959
+ }
960
+ if (enableInference && resolvedAgent) {
961
+ try {
962
+ const inferred = await this.inferDependenciesWithAgent(resolvedAgent, nodes, {
963
+ project,
964
+ epic,
965
+ story,
966
+ docContext,
967
+ stream: agentStream,
968
+ warnings,
969
+ });
970
+ await this.applyInferredDependencies(tasks, deps, inferred, warnings);
971
+ ({ nodes, dependents, missingRefs } = this.buildNodes(tasks, deps));
972
+ }
973
+ catch (error) {
974
+ warnings.push(`Dependency inference skipped: ${error.message}`);
975
+ }
976
+ }
977
+ else if (enableInference && !resolvedAgent) {
978
+ warnings.push("Dependency inference skipped: no agent resolved.");
979
+ }
980
+ if (missingRefs.size > 0) {
981
+ warnings.push(`Missing dependencies referenced: ${Array.from(missingRefs).join(", ")}`);
982
+ }
983
+ const missingContext = await this.loadMissingContext(nodes.map((node) => node.id));
984
+ if (missingContext.size > 0) {
985
+ warnings.push(`Tasks with open missing_context comments: ${Array.from(missingContext).length}`);
986
+ }
987
+ const stageOrder = (request.stageOrder && request.stageOrder.length > 0
988
+ ? request.stageOrder
989
+ : DEFAULT_STAGE_ORDER);
990
+ const stageOrderMap = new Map();
991
+ for (const [idx, stage] of stageOrder.entries()) {
992
+ if (["foundation", "backend", "frontend", "other"].includes(stage)) {
993
+ stageOrderMap.set(stage, idx);
994
+ }
995
+ }
996
+ if (stageOrderMap.size === 0) {
997
+ DEFAULT_STAGE_ORDER.forEach((stage, idx) => stageOrderMap.set(stage, idx));
998
+ }
999
+ const impact = this.dependencyImpactMap(dependents);
1000
+ const { ordered: initialOrder, cycle, cycleMembers } = this.topologicalSort(nodes, dependents, impact, undefined, stageOrderMap);
1001
+ if (cycle) {
1002
+ warnings.push("Dependency cycle detected; ordering may be partial.");
1003
+ }
1004
+ let agentRank;
1005
+ if (enableAgentRanking && resolvedAgent) {
546
1006
  try {
547
- const agent = await this.resolveAgent(request.agentName);
548
1007
  const summary = {
549
1008
  project: project.key,
550
1009
  epic: epic?.key,
@@ -573,13 +1032,12 @@ export class TaskOrderingService {
573
1032
  ]
574
1033
  .filter(Boolean)
575
1034
  .join("\n\n");
576
- const { output } = await this.invokeAgent(agent, prompt, request.agentStream !== false, {
1035
+ const { output } = await this.invokeAgent(resolvedAgent, prompt, agentStream, {
577
1036
  command: "order-tasks",
578
1037
  project: project.key,
579
1038
  epic: epic?.key,
580
1039
  story: story?.key,
581
1040
  statuses,
582
- includeBlocked: request.includeBlocked === true,
583
1041
  });
584
1042
  const promptTokens = estimateTokens(prompt);
585
1043
  const completionTokens = estimateTokens(output);
@@ -589,8 +1047,8 @@ export class TaskOrderingService {
589
1047
  projectId: project.id,
590
1048
  commandRunId: commandRun.id,
591
1049
  jobId: job?.id,
592
- agentId: agent.id,
593
- modelName: agent.defaultModel,
1050
+ agentId: resolvedAgent.id,
1051
+ modelName: resolvedAgent.defaultModel,
594
1052
  timestamp: new Date().toISOString(),
595
1053
  commandName: "order-tasks",
596
1054
  action: "ordering_tasks",
@@ -600,13 +1058,14 @@ export class TaskOrderingService {
600
1058
  tokensCompletion: completionTokens,
601
1059
  tokensTotal: promptTokens + completionTokens,
602
1060
  metadata: {
603
- adapter: agent.adapter,
1061
+ adapter: resolvedAgent.adapter,
604
1062
  epicKey: epic?.key,
605
1063
  storyKey: story?.key,
606
- includeBlocked: request.includeBlocked === true,
607
1064
  statusFilter: statuses,
608
- agentSlug: agent.slug,
609
- modelName: agent.defaultModel,
1065
+ agentSlug: resolvedAgent.slug,
1066
+ modelName: resolvedAgent.defaultModel,
1067
+ phase: "agent_ordering",
1068
+ attempt: 1,
610
1069
  },
611
1070
  });
612
1071
  }
@@ -616,14 +1075,15 @@ export class TaskOrderingService {
616
1075
  warnings.push(`Agent refinement skipped: ${error.message}`);
617
1076
  }
618
1077
  }
619
- const { ordered, cycle: cycleAfterAgent, cycleMembers: agentCycleMembers } = this.topologicalSort(nodes, dependents, impact, agentRank);
1078
+ else if (enableAgentRanking && !resolvedAgent) {
1079
+ warnings.push("Agent refinement skipped: no agent resolved.");
1080
+ }
1081
+ const { ordered, cycle: cycleAfterAgent, cycleMembers: agentCycleMembers } = this.topologicalSort(nodes, dependents, impact, agentRank, stageOrderMap);
620
1082
  const finalCycleMembers = new Set([...cycleMembers, ...agentCycleMembers]);
621
1083
  if (cycleAfterAgent && !cycle) {
622
1084
  warnings.push("Agent-influenced ordering encountered a cycle; used partial order.");
623
1085
  }
624
- const blockedTasks = ordered.filter((t) => blockedSet.has(t.id));
625
- const unblockedTasks = ordered.filter((t) => !blockedSet.has(t.id));
626
- const prioritized = [...unblockedTasks, ...blockedTasks];
1086
+ const prioritized = ordered;
627
1087
  const epicMap = new Map();
628
1088
  const storyMap = new Map();
629
1089
  prioritized.forEach((task, idx) => {
@@ -636,16 +1096,13 @@ export class TaskOrderingService {
636
1096
  storyMap.set(task.story_id, storyTasks);
637
1097
  });
638
1098
  await this.persistPriorities(prioritized, epicMap, storyMap);
639
- const mapped = this.mapResult(prioritized, blockedSet, impact, finalCycleMembers);
640
- const visibleOrdered = request.includeBlocked ? mapped.ordered : mapped.ordered.filter((t) => !t.blocked);
641
- const visibleBlocked = request.includeBlocked ? [] : mapped.blocked;
1099
+ const mapped = this.mapResult(prioritized, impact, finalCycleMembers);
642
1100
  if (job) {
643
1101
  await this.jobService.updateJobStatus(job.id, "completed", {
644
1102
  processedItems: mapped.ordered.length,
645
1103
  payload: {
646
1104
  warnings,
647
1105
  statuses,
648
- includeBlocked: request.includeBlocked === true,
649
1106
  epicKey: epic?.key,
650
1107
  storyKey: story?.key,
651
1108
  },
@@ -657,8 +1114,7 @@ export class TaskOrderingService {
657
1114
  return {
658
1115
  project,
659
1116
  epic,
660
- ordered: visibleOrdered,
661
- blocked: visibleBlocked,
1117
+ ordered: mapped.ordered,
662
1118
  warnings,
663
1119
  jobId: job?.id,
664
1120
  commandRunId: commandRun?.id,