@mcoda/core 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +9 -0
  4. package/dist/api/AgentsApi.d.ts +36 -0
  5. package/dist/api/AgentsApi.d.ts.map +1 -0
  6. package/dist/api/AgentsApi.js +176 -0
  7. package/dist/api/QaTasksApi.d.ts +8 -0
  8. package/dist/api/QaTasksApi.d.ts.map +1 -0
  9. package/dist/api/QaTasksApi.js +36 -0
  10. package/dist/api/TasksApi.d.ts +7 -0
  11. package/dist/api/TasksApi.d.ts.map +1 -0
  12. package/dist/api/TasksApi.js +34 -0
  13. package/dist/config/ConfigService.d.ts +3 -0
  14. package/dist/config/ConfigService.d.ts.map +1 -0
  15. package/dist/config/ConfigService.js +2 -0
  16. package/dist/domain/dependencies/Dependency.d.ts +3 -0
  17. package/dist/domain/dependencies/Dependency.d.ts.map +1 -0
  18. package/dist/domain/dependencies/Dependency.js +2 -0
  19. package/dist/domain/epics/Epic.d.ts +3 -0
  20. package/dist/domain/epics/Epic.d.ts.map +1 -0
  21. package/dist/domain/epics/Epic.js +2 -0
  22. package/dist/domain/projects/Project.d.ts +3 -0
  23. package/dist/domain/projects/Project.d.ts.map +1 -0
  24. package/dist/domain/projects/Project.js +2 -0
  25. package/dist/domain/tasks/Task.d.ts +3 -0
  26. package/dist/domain/tasks/Task.d.ts.map +1 -0
  27. package/dist/domain/tasks/Task.js +2 -0
  28. package/dist/domain/userStories/UserStory.d.ts +3 -0
  29. package/dist/domain/userStories/UserStory.d.ts.map +1 -0
  30. package/dist/domain/userStories/UserStory.js +2 -0
  31. package/dist/index.d.ts +28 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +27 -0
  34. package/dist/prompts/PdrPrompts.d.ts +4 -0
  35. package/dist/prompts/PdrPrompts.d.ts.map +1 -0
  36. package/dist/prompts/PdrPrompts.js +21 -0
  37. package/dist/prompts/PromptLoader.d.ts +3 -0
  38. package/dist/prompts/PromptLoader.d.ts.map +1 -0
  39. package/dist/prompts/PromptLoader.js +2 -0
  40. package/dist/prompts/SdsPrompts.d.ts +5 -0
  41. package/dist/prompts/SdsPrompts.d.ts.map +1 -0
  42. package/dist/prompts/SdsPrompts.js +44 -0
  43. package/dist/services/agents/AgentManagementService.d.ts +3 -0
  44. package/dist/services/agents/AgentManagementService.d.ts.map +1 -0
  45. package/dist/services/agents/AgentManagementService.js +2 -0
  46. package/dist/services/agents/GatewayAgentService.d.ts +92 -0
  47. package/dist/services/agents/GatewayAgentService.d.ts.map +1 -0
  48. package/dist/services/agents/GatewayAgentService.js +870 -0
  49. package/dist/services/agents/RoutingApiClient.d.ts +23 -0
  50. package/dist/services/agents/RoutingApiClient.d.ts.map +1 -0
  51. package/dist/services/agents/RoutingApiClient.js +62 -0
  52. package/dist/services/agents/RoutingService.d.ts +50 -0
  53. package/dist/services/agents/RoutingService.d.ts.map +1 -0
  54. package/dist/services/agents/RoutingService.js +386 -0
  55. package/dist/services/agents/generated/RoutingApiClient.d.ts +21 -0
  56. package/dist/services/agents/generated/RoutingApiClient.d.ts.map +1 -0
  57. package/dist/services/agents/generated/RoutingApiClient.js +68 -0
  58. package/dist/services/backlog/BacklogService.d.ts +98 -0
  59. package/dist/services/backlog/BacklogService.d.ts.map +1 -0
  60. package/dist/services/backlog/BacklogService.js +453 -0
  61. package/dist/services/backlog/TaskOrderingService.d.ts +88 -0
  62. package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -0
  63. package/dist/services/backlog/TaskOrderingService.js +675 -0
  64. package/dist/services/docs/DocsService.d.ts +82 -0
  65. package/dist/services/docs/DocsService.d.ts.map +1 -0
  66. package/dist/services/docs/DocsService.js +1631 -0
  67. package/dist/services/estimate/EstimateService.d.ts +12 -0
  68. package/dist/services/estimate/EstimateService.d.ts.map +1 -0
  69. package/dist/services/estimate/EstimateService.js +103 -0
  70. package/dist/services/estimate/VelocityService.d.ts +19 -0
  71. package/dist/services/estimate/VelocityService.d.ts.map +1 -0
  72. package/dist/services/estimate/VelocityService.js +237 -0
  73. package/dist/services/estimate/types.d.ts +30 -0
  74. package/dist/services/estimate/types.d.ts.map +1 -0
  75. package/dist/services/estimate/types.js +1 -0
  76. package/dist/services/execution/ExecutionService.d.ts +3 -0
  77. package/dist/services/execution/ExecutionService.d.ts.map +1 -0
  78. package/dist/services/execution/ExecutionService.js +2 -0
  79. package/dist/services/execution/QaFollowupService.d.ts +38 -0
  80. package/dist/services/execution/QaFollowupService.d.ts.map +1 -0
  81. package/dist/services/execution/QaFollowupService.js +236 -0
  82. package/dist/services/execution/QaProfileService.d.ts +22 -0
  83. package/dist/services/execution/QaProfileService.d.ts.map +1 -0
  84. package/dist/services/execution/QaProfileService.js +142 -0
  85. package/dist/services/execution/QaTasksService.d.ts +101 -0
  86. package/dist/services/execution/QaTasksService.d.ts.map +1 -0
  87. package/dist/services/execution/QaTasksService.js +1117 -0
  88. package/dist/services/execution/TaskSelectionService.d.ts +50 -0
  89. package/dist/services/execution/TaskSelectionService.d.ts.map +1 -0
  90. package/dist/services/execution/TaskSelectionService.js +281 -0
  91. package/dist/services/execution/TaskStateService.d.ts +19 -0
  92. package/dist/services/execution/TaskStateService.d.ts.map +1 -0
  93. package/dist/services/execution/TaskStateService.js +59 -0
  94. package/dist/services/execution/WorkOnTasksService.d.ts +80 -0
  95. package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -0
  96. package/dist/services/execution/WorkOnTasksService.js +1833 -0
  97. package/dist/services/jobs/JobInsightsService.d.ts +97 -0
  98. package/dist/services/jobs/JobInsightsService.d.ts.map +1 -0
  99. package/dist/services/jobs/JobInsightsService.js +263 -0
  100. package/dist/services/jobs/JobResumeService.d.ts +16 -0
  101. package/dist/services/jobs/JobResumeService.d.ts.map +1 -0
  102. package/dist/services/jobs/JobResumeService.js +113 -0
  103. package/dist/services/jobs/JobService.d.ts +149 -0
  104. package/dist/services/jobs/JobService.d.ts.map +1 -0
  105. package/dist/services/jobs/JobService.js +490 -0
  106. package/dist/services/jobs/JobsApiClient.d.ts +73 -0
  107. package/dist/services/jobs/JobsApiClient.d.ts.map +1 -0
  108. package/dist/services/jobs/JobsApiClient.js +67 -0
  109. package/dist/services/openapi/OpenApiService.d.ts +54 -0
  110. package/dist/services/openapi/OpenApiService.d.ts.map +1 -0
  111. package/dist/services/openapi/OpenApiService.js +503 -0
  112. package/dist/services/planning/CreateTasksService.d.ts +68 -0
  113. package/dist/services/planning/CreateTasksService.d.ts.map +1 -0
  114. package/dist/services/planning/CreateTasksService.js +989 -0
  115. package/dist/services/planning/KeyHelpers.d.ts +5 -0
  116. package/dist/services/planning/KeyHelpers.d.ts.map +1 -0
  117. package/dist/services/planning/KeyHelpers.js +62 -0
  118. package/dist/services/planning/PlanningService.d.ts +3 -0
  119. package/dist/services/planning/PlanningService.d.ts.map +1 -0
  120. package/dist/services/planning/PlanningService.js +2 -0
  121. package/dist/services/planning/RefineTasksService.d.ts +56 -0
  122. package/dist/services/planning/RefineTasksService.d.ts.map +1 -0
  123. package/dist/services/planning/RefineTasksService.js +1328 -0
  124. package/dist/services/review/CodeReviewService.d.ts +103 -0
  125. package/dist/services/review/CodeReviewService.d.ts.map +1 -0
  126. package/dist/services/review/CodeReviewService.js +1187 -0
  127. package/dist/services/system/SystemUpdateService.d.ts +55 -0
  128. package/dist/services/system/SystemUpdateService.d.ts.map +1 -0
  129. package/dist/services/system/SystemUpdateService.js +136 -0
  130. package/dist/services/tasks/TaskApiResolver.d.ts +7 -0
  131. package/dist/services/tasks/TaskApiResolver.d.ts.map +1 -0
  132. package/dist/services/tasks/TaskApiResolver.js +41 -0
  133. package/dist/services/tasks/TaskDetailService.d.ts +106 -0
  134. package/dist/services/tasks/TaskDetailService.d.ts.map +1 -0
  135. package/dist/services/tasks/TaskDetailService.js +332 -0
  136. package/dist/services/telemetry/TelemetryService.d.ts +53 -0
  137. package/dist/services/telemetry/TelemetryService.d.ts.map +1 -0
  138. package/dist/services/telemetry/TelemetryService.js +434 -0
  139. package/dist/workspace/WorkspaceManager.d.ts +35 -0
  140. package/dist/workspace/WorkspaceManager.d.ts.map +1 -0
  141. package/dist/workspace/WorkspaceManager.js +201 -0
  142. package/package.json +45 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenApiService.d.ts","sourceRoot":"","sources":["../../../src/services/openapi/OpenApiService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AAGnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA0MD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,SAAS,CAAsB;gBAGrC,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,YAAY,CAAC;QAAC,UAAU,CAAC,EAAE,UAAU,CAAC;QAAC,YAAY,EAAE,YAAY,CAAC;QAAC,cAAc,EAAE,cAAc,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE;WAShI,MAAM,CAAC,SAAS,EAAE,mBAAmB,EAAE,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAY/G,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAKd,YAAY;YASZ,WAAW;IA8BzB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,YAAY;YAaN,mBAAmB;IAajC,OAAO,CAAC,WAAW;YAwBL,gBAAgB;YAMhB,cAAc;YAOd,eAAe;YAef,oBAAoB;IAS5B,gBAAgB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA0JxF"}
@@ -0,0 +1,503 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import YAML from "yaml";
4
+ import SwaggerParser from "@apidevtools/swagger-parser";
5
+ import { AgentService } from "@mcoda/agents";
6
+ import { DocdexClient } from "@mcoda/integrations";
7
+ import { GlobalRepository } from "@mcoda/db";
8
+ import { JobService } from "../jobs/JobService.js";
9
+ import { RoutingService } from "../agents/RoutingService.js";
10
+ const OPENAPI_TAGS = [
11
+ // For project-specific specs, tags come from context; this is only a fallback.
12
+ ];
13
+ const OPENAPI_VERSION = "3.1.0";
14
+ const CONTEXT_TOKEN_BUDGET = 8000;
15
+ const estimateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
16
+ const fileExists = async (candidate) => {
17
+ try {
18
+ await fs.access(candidate);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ };
25
+ const readGitBranch = async (workspaceRoot) => {
26
+ const headPath = path.join(workspaceRoot, ".git", "HEAD");
27
+ try {
28
+ const content = await fs.readFile(headPath, "utf8");
29
+ const match = content.match(/ref: refs\/heads\/(.+)/);
30
+ return match ? match[1].trim() : content.trim();
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ };
36
+ class OpenapiContextAssembler {
37
+ constructor(docdex, workspace, projectKey) {
38
+ this.docdex = docdex;
39
+ this.workspace = workspace;
40
+ this.projectKey = projectKey;
41
+ }
42
+ summarize(doc) {
43
+ const text = doc.content ?? "";
44
+ if (!text)
45
+ return doc.title ?? doc.path ?? doc.id ?? "Document";
46
+ return text.split(/\r?\n/).slice(0, 5).join(" ").slice(0, 400);
47
+ }
48
+ async findLatestLocalDoc(docType) {
49
+ const candidates = [];
50
+ const dirNames = [".mcoda/docs", "docs"];
51
+ for (const dir of dirNames) {
52
+ const target = path.join(this.workspace.workspaceRoot, dir, docType.toLowerCase());
53
+ try {
54
+ const entries = await fs.readdir(target);
55
+ for (const entry of entries.filter((e) => e.endsWith(".md"))) {
56
+ const full = path.join(target, entry);
57
+ const stat = await fs.stat(full);
58
+ candidates.push({ path: full, mtime: stat.mtimeMs });
59
+ }
60
+ }
61
+ catch {
62
+ // ignore
63
+ }
64
+ }
65
+ const latest = candidates.sort((a, b) => b.mtime - a.mtime)[0];
66
+ if (!latest)
67
+ return undefined;
68
+ const content = await fs.readFile(latest.path, "utf8");
69
+ const timestamp = new Date(latest.mtime).toISOString();
70
+ return {
71
+ id: `local-${docType.toLowerCase()}-${path.basename(latest.path)}`,
72
+ docType,
73
+ path: latest.path,
74
+ content,
75
+ createdAt: timestamp,
76
+ updatedAt: timestamp,
77
+ };
78
+ }
79
+ formatBlock(doc, label, priority, maxSegments = 5) {
80
+ const segments = (doc.segments ?? []).slice(0, maxSegments);
81
+ const heading = `[${doc.docType}] ${label}`;
82
+ const source = doc.path ?? doc.id ?? label;
83
+ const body = segments.length
84
+ ? segments
85
+ .map((seg, idx) => {
86
+ const head = seg.heading ?? `Segment ${idx + 1}`;
87
+ const trimmed = seg.content.length > 1000 ? `${seg.content.slice(0, 1000)}...` : seg.content;
88
+ return `### ${head}\n${trimmed}`;
89
+ })
90
+ .join("\n\n")
91
+ : doc.content ?? this.summarize(doc);
92
+ const content = [heading, `Source: ${source}`, body].filter(Boolean).join("\n");
93
+ return {
94
+ label,
95
+ content,
96
+ priority,
97
+ tokens: estimateTokens(content),
98
+ };
99
+ }
100
+ enforceBudget(blocks, warnings) {
101
+ let total = blocks.reduce((sum, b) => sum + b.tokens, 0);
102
+ if (total <= CONTEXT_TOKEN_BUDGET)
103
+ return blocks;
104
+ const ordered = [...blocks].sort((a, b) => a.priority - b.priority);
105
+ for (const block of ordered) {
106
+ if (total <= CONTEXT_TOKEN_BUDGET)
107
+ break;
108
+ const summary = `${block.label}: ${block.content.slice(0, 400)}`;
109
+ total -= block.tokens;
110
+ block.content = summary;
111
+ block.tokens = estimateTokens(summary);
112
+ total += block.tokens;
113
+ warnings.push(`Context for ${block.label} truncated to fit budget.`);
114
+ }
115
+ return blocks;
116
+ }
117
+ async build() {
118
+ const warnings = [];
119
+ const blocks = [];
120
+ let docdexAvailable = true;
121
+ let sdsDocs = [];
122
+ let pdrDocs = [];
123
+ let rfpDocs = [];
124
+ let openapiDocs = [];
125
+ let schemaDocs = [];
126
+ try {
127
+ [sdsDocs, pdrDocs, rfpDocs, openapiDocs, schemaDocs] = await Promise.all([
128
+ this.docdex.search({ docType: "SDS", profile: "openapi", projectKey: this.projectKey }),
129
+ this.docdex.search({ docType: "PDR", profile: "openapi", projectKey: this.projectKey }),
130
+ this.docdex.search({ docType: "RFP", profile: "openapi", projectKey: this.projectKey }),
131
+ this.docdex.search({ docType: "OPENAPI", profile: "openapi", projectKey: this.projectKey }),
132
+ this.docdex.search({ docType: "Architecture", profile: "openapi", projectKey: this.projectKey }),
133
+ ]);
134
+ }
135
+ catch (error) {
136
+ docdexAvailable = false;
137
+ warnings.push(`Docdex unavailable; falling back to local docs for OpenAPI context (${error.message ?? "unknown"})`);
138
+ }
139
+ // Fallbacks when docdex returns no hits
140
+ if (sdsDocs.length === 0) {
141
+ const local = await this.findLatestLocalDoc("SDS");
142
+ if (local) {
143
+ blocks.push(this.formatBlock(local, "Local SDS (no docdex)", 1, 8));
144
+ warnings.push("No SDS found in docdex; using latest local SDS file.");
145
+ sdsDocs = [local];
146
+ }
147
+ else {
148
+ warnings.push("No SDS found in docdex or local workspace.");
149
+ }
150
+ }
151
+ else {
152
+ blocks.push(this.formatBlock(sdsDocs[0], "SDS OpenAPI contract", 1, 8));
153
+ }
154
+ if (pdrDocs.length === 0) {
155
+ const local = await this.findLatestLocalDoc("PDR");
156
+ if (local) {
157
+ blocks.push(this.formatBlock(local, "Local PDR (no docdex)", 2, 6));
158
+ warnings.push("No PDR found in docdex; using latest local PDR file.");
159
+ pdrDocs = [local];
160
+ }
161
+ else {
162
+ warnings.push("No PDR found in docdex or local workspace.");
163
+ }
164
+ }
165
+ else {
166
+ blocks.push(this.formatBlock(pdrDocs[0], "PDR context", 2, 6));
167
+ }
168
+ if (rfpDocs.length === 0) {
169
+ const local = await this.findLatestLocalDoc("RFP");
170
+ if (local) {
171
+ blocks.push(this.formatBlock(local, "Local RFP (no docdex)", 2, 6));
172
+ warnings.push("No RFP found in docdex; using latest local RFP file.");
173
+ rfpDocs = [local];
174
+ }
175
+ else {
176
+ warnings.push("No RFP found in docdex or local workspace.");
177
+ }
178
+ }
179
+ else {
180
+ blocks.push(this.formatBlock(rfpDocs[0], "RFP alignment", 2, 6));
181
+ }
182
+ if (openapiDocs.length > 0) {
183
+ blocks.push(this.formatBlock(openapiDocs[0], "Existing OpenAPI docdex", 1, 6));
184
+ }
185
+ if (schemaDocs.length > 0) {
186
+ blocks.push(this.formatBlock(schemaDocs[0], "Data model & persistence", 1, 6));
187
+ }
188
+ if (!blocks.length) {
189
+ const localSds = await this.findLatestLocalDoc("SDS");
190
+ if (localSds)
191
+ blocks.push(this.formatBlock(localSds, "Local SDS", 1, 6));
192
+ const localPdr = await this.findLatestLocalDoc("PDR");
193
+ if (localPdr)
194
+ blocks.push(this.formatBlock(localPdr, "Local PDR", 2, 6));
195
+ }
196
+ return {
197
+ blocks: this.enforceBudget(blocks, warnings),
198
+ docdexAvailable,
199
+ warnings,
200
+ };
201
+ }
202
+ }
203
+ export class OpenApiService {
204
+ constructor(workspace, deps) {
205
+ this.workspace = workspace;
206
+ this.docdex = deps?.docdex ?? new DocdexClient({ workspaceRoot: workspace.workspaceRoot });
207
+ this.jobService = deps?.jobService ?? new JobService(workspace, undefined, { noTelemetry: deps?.noTelemetry });
208
+ this.agentService = deps.agentService;
209
+ this.routingService = deps.routingService;
210
+ }
211
+ static async create(workspace, options = {}) {
212
+ const repo = await GlobalRepository.create();
213
+ const agentService = new AgentService(repo);
214
+ const routingService = await RoutingService.create();
215
+ const docdex = new DocdexClient({
216
+ workspaceRoot: workspace.workspaceRoot,
217
+ baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
218
+ });
219
+ const jobService = new JobService(workspace, undefined, { noTelemetry: options.noTelemetry });
220
+ return new OpenApiService(workspace, { agentService, routingService, docdex, jobService, noTelemetry: options.noTelemetry });
221
+ }
222
+ async close() {
223
+ if (this.agentService.close)
224
+ await this.agentService.close();
225
+ if (this.jobService.close)
226
+ await this.jobService.close();
227
+ }
228
+ async resolveAgent(agentName) {
229
+ const resolved = await this.routingService.resolveAgentForCommand({
230
+ workspace: this.workspace,
231
+ commandName: "openapi-from-docs",
232
+ overrideAgentSlug: agentName,
233
+ });
234
+ return resolved.agent;
235
+ }
236
+ async invokeAgent(agent, prompt, stream, jobId, onToken) {
237
+ if (stream) {
238
+ try {
239
+ const generator = await this.agentService.invokeStream(agent.id, { input: prompt, metadata: { jobId } });
240
+ const chunks = [];
241
+ for await (const chunk of generator) {
242
+ chunks.push(chunk.output);
243
+ await this.jobService.appendLog(jobId, chunk.output);
244
+ if (onToken)
245
+ onToken(chunk.output);
246
+ }
247
+ return { output: chunks.join(""), adapter: agent.adapter };
248
+ }
249
+ catch {
250
+ const fallback = await this.agentService.invoke(agent.id, { input: prompt, metadata: { jobId } });
251
+ await this.jobService.appendLog(jobId, fallback.output);
252
+ if (onToken)
253
+ onToken(fallback.output);
254
+ return { output: fallback.output, adapter: fallback.adapter, metadata: fallback.metadata };
255
+ }
256
+ }
257
+ const result = await this.agentService.invoke(agent.id, { input: prompt, metadata: { jobId } });
258
+ await this.jobService.appendLog(jobId, result.output);
259
+ if (onToken)
260
+ onToken(result.output);
261
+ return { output: result.output, adapter: result.adapter, metadata: result.metadata };
262
+ }
263
+ sanitizeOutput(raw) {
264
+ const trimmed = raw.trim();
265
+ if (trimmed.startsWith("```")) {
266
+ const withoutFence = trimmed.replace(/^```[a-zA-Z]*\s*/m, "").replace(/```$/, "");
267
+ return withoutFence.trim();
268
+ }
269
+ return trimmed;
270
+ }
271
+ validateSpec(doc) {
272
+ const errors = [];
273
+ if (!doc || typeof doc !== "object") {
274
+ errors.push("Spec is not a YAML object.");
275
+ return errors;
276
+ }
277
+ if (!doc.openapi)
278
+ errors.push("Missing openapi version");
279
+ if (!doc.info?.title)
280
+ errors.push("Missing info.title");
281
+ if (!doc.info?.version)
282
+ errors.push("Missing info.version");
283
+ if (!doc.paths)
284
+ errors.push("paths section is required (can be empty if no HTTP API)");
285
+ return errors;
286
+ }
287
+ async runOpenapiValidator(doc) {
288
+ try {
289
+ await SwaggerParser.validate(doc);
290
+ return [];
291
+ }
292
+ catch (error) {
293
+ const details = error?.details;
294
+ if (Array.isArray(details) && details.length) {
295
+ return details.map((d) => d.message ?? String(error));
296
+ }
297
+ return [error.message];
298
+ }
299
+ }
300
+ buildPrompt(context, cliVersion, retryReasons) {
301
+ const contextBlocks = context.blocks
302
+ .map((block) => `### ${block.label}\n${block.content}`)
303
+ .join("\n\n");
304
+ const retryNote = retryReasons?.length
305
+ ? `\nPrevious attempt issues:\n${retryReasons.map((r) => `- ${r}`).join("\n")}\nFix them in this draft.\n`
306
+ : "";
307
+ return [
308
+ "You are generating an OpenAPI 3.1 YAML for THIS workspace/project using only the provided PDR/SDS/RFP context.",
309
+ "Derive resources, schemas, and HTTP endpoints directly from the product requirements (e.g., todos CRUD, filters, search, bulk actions).",
310
+ "If the documents describe a frontend-only/localStorage app, design a minimal REST API that could back those features (e.g., /todos, /todos/{id}, bulk operations, search/filter params) instead of returning an empty spec.",
311
+ "Prefer concise tags derived from domain resources (e.g., Todos). Avoid generic mcoda/system endpoints unless explicitly described in the context.",
312
+ `Use OpenAPI version ${OPENAPI_VERSION}, set info.title to the project name from context (fallback \"mcoda API\"), and info.version ${cliVersion}.`,
313
+ "Return only valid YAML (no Markdown fences, no commentary).",
314
+ retryNote,
315
+ "Scope rules:",
316
+ "- Derive endpoints, schemas, and tags from the provided PDR/SDS/RFP context only.",
317
+ "- Do NOT emit generic mcoda CLI/system endpoints unless explicitly described.",
318
+ "- Prefer concise schemas and operations that map to described APIs; omit unused boilerplate.",
319
+ "Context:",
320
+ contextBlocks || "No context available; if none, produce a minimal empty spec with paths: {}.",
321
+ ].join("\n\n");
322
+ }
323
+ async ensureOpenapiDir() {
324
+ const dir = path.join(this.workspace.workspaceRoot, "openapi");
325
+ await fs.mkdir(dir, { recursive: true });
326
+ return dir;
327
+ }
328
+ async backupIfNeeded(target) {
329
+ if (!(await fileExists(target)))
330
+ return undefined;
331
+ const backup = `${target}.bak`;
332
+ await fs.copyFile(target, backup);
333
+ return backup;
334
+ }
335
+ async registerOpenapi(outPath, content) {
336
+ const branch = this.workspace.config?.branch ?? (await readGitBranch(this.workspace.workspaceRoot));
337
+ return this.docdex.registerDocument({
338
+ docType: "OPENAPI",
339
+ path: outPath,
340
+ content,
341
+ metadata: {
342
+ workspace: this.workspace.workspaceId,
343
+ branch,
344
+ status: "canonical",
345
+ projectKey: this.workspace.config?.projectKey,
346
+ },
347
+ });
348
+ }
349
+ async validateExistingSpec(target) {
350
+ const content = await fs.readFile(target, "utf8");
351
+ const parsed = YAML.parse(content);
352
+ const issues = this.validateSpec(parsed);
353
+ const validatorIssues = await this.runOpenapiValidator(parsed);
354
+ issues.push(...validatorIssues);
355
+ return { spec: content, issues };
356
+ }
357
+ async generateFromDocs(options) {
358
+ const commandRun = await this.jobService.startCommandRun("openapi-from-docs", options.projectKey);
359
+ const job = await this.jobService.startJob("openapi_change", commandRun.id, options.projectKey, {
360
+ commandName: commandRun.commandName,
361
+ payload: { workspaceRoot: this.workspace.workspaceRoot, projectKey: options.projectKey },
362
+ });
363
+ const warnings = [];
364
+ try {
365
+ const projectKey = options.projectKey ?? this.workspace.config?.projectKey;
366
+ const assembler = new OpenapiContextAssembler(this.docdex, this.workspace, projectKey);
367
+ const context = await assembler.build();
368
+ warnings.push(...context.warnings);
369
+ await this.jobService.writeCheckpoint(job.id, {
370
+ stage: "context_built",
371
+ timestamp: new Date().toISOString(),
372
+ details: { docdexAvailable: context.docdexAvailable },
373
+ });
374
+ await this.jobService.recordTokenUsage({
375
+ timestamp: new Date().toISOString(),
376
+ workspaceId: this.workspace.workspaceId,
377
+ commandName: "openapi-from-docs",
378
+ jobId: job.id,
379
+ action: "docdex_context",
380
+ promptTokens: 0,
381
+ completionTokens: 0,
382
+ metadata: { docdexAvailable: context.docdexAvailable },
383
+ });
384
+ const openapiDir = await this.ensureOpenapiDir();
385
+ const outputPath = path.join(openapiDir, "mcoda.yaml");
386
+ if (options.validateOnly) {
387
+ if (!(await fileExists(outputPath))) {
388
+ throw new Error(`Cannot validate missing spec: ${outputPath}`);
389
+ }
390
+ const { spec, issues } = await this.validateExistingSpec(outputPath);
391
+ const validationNote = issues.length ? `Validation issues:\n${issues.join("\n")}` : "Validation passed.";
392
+ await this.jobService.appendLog(job.id, `${validationNote}\n`);
393
+ const jobState = issues.length ? "failed" : "completed";
394
+ const commandState = issues.length ? "failed" : "succeeded";
395
+ await this.jobService.updateJobStatus(job.id, jobState, { payload: { validation: validationNote } });
396
+ await this.jobService.finishCommandRun(commandRun.id, commandState, issues.join("; "));
397
+ return {
398
+ jobId: job.id,
399
+ commandRunId: commandRun.id,
400
+ outputPath,
401
+ spec,
402
+ warnings,
403
+ };
404
+ }
405
+ if (!options.force && (await fileExists(outputPath))) {
406
+ throw new Error(`File exists, use --force to overwrite (${outputPath})`);
407
+ }
408
+ const agent = await this.resolveAgent(options.agentName);
409
+ const stream = options.agentStream ?? true;
410
+ let specYaml = "";
411
+ let parsed;
412
+ let adapter = agent.adapter;
413
+ let agentMetadata;
414
+ let lastErrors;
415
+ for (let attempt = 0; attempt < 2; attempt += 1) {
416
+ const prompt = this.buildPrompt(context, options.cliVersion, lastErrors);
417
+ const { output, adapter: usedAdapter, metadata } = await this.invokeAgent(agent, prompt, stream, job.id, options.onToken);
418
+ adapter = usedAdapter;
419
+ agentMetadata = metadata;
420
+ specYaml = this.sanitizeOutput(output);
421
+ try {
422
+ parsed = YAML.parse(specYaml);
423
+ if (!parsed.info)
424
+ parsed.info = {};
425
+ const projectTitle = options.projectKey ?? this.workspace.config?.projectKey;
426
+ parsed.info.title = parsed.info.title ?? projectTitle ?? "mcoda API";
427
+ parsed.info.version = options.cliVersion;
428
+ parsed.openapi = parsed.openapi ?? OPENAPI_VERSION;
429
+ const errors = this.validateSpec(parsed);
430
+ const validatorErrors = await this.runOpenapiValidator(parsed);
431
+ errors.push(...validatorErrors);
432
+ await this.jobService.recordTokenUsage({
433
+ timestamp: new Date().toISOString(),
434
+ workspaceId: this.workspace.workspaceId,
435
+ commandName: "openapi-from-docs",
436
+ jobId: job.id,
437
+ agentId: agent.id,
438
+ modelName: agent.defaultModel,
439
+ action: attempt === 0 ? "draft_openapi" : "draft_openapi_retry",
440
+ promptTokens: estimateTokens(prompt),
441
+ completionTokens: estimateTokens(output),
442
+ metadata: { adapter, provider: adapter, attempt },
443
+ });
444
+ if (errors.length === 0) {
445
+ specYaml = YAML.stringify(parsed);
446
+ break;
447
+ }
448
+ if (attempt === 1) {
449
+ throw new Error(`Generated spec failed validation: ${errors.join("; ")}`);
450
+ }
451
+ lastErrors = errors;
452
+ }
453
+ catch (error) {
454
+ if (attempt === 1) {
455
+ throw new Error(error.message || "Failed to parse generated YAML");
456
+ }
457
+ lastErrors = [error.message ?? "Invalid YAML"];
458
+ }
459
+ }
460
+ let backup;
461
+ if (!options.dryRun) {
462
+ backup = await this.backupIfNeeded(outputPath);
463
+ await fs.writeFile(outputPath, specYaml, "utf8");
464
+ }
465
+ else {
466
+ warnings.push("Dry run enabled; spec not written to disk.");
467
+ }
468
+ let docdexId;
469
+ if (!options.dryRun && context.docdexAvailable) {
470
+ try {
471
+ const registered = await this.registerOpenapi(outputPath, specYaml);
472
+ docdexId = registered.id;
473
+ }
474
+ catch (error) {
475
+ warnings.push(`Docdex registration skipped: ${error.message}`);
476
+ }
477
+ }
478
+ await this.jobService.updateJobStatus(job.id, "completed", {
479
+ payload: {
480
+ outputPath,
481
+ backupPath: backup,
482
+ docdexId,
483
+ adapter,
484
+ agentMetadata,
485
+ },
486
+ });
487
+ await this.jobService.finishCommandRun(commandRun.id, "succeeded");
488
+ return {
489
+ jobId: job.id,
490
+ commandRunId: commandRun.id,
491
+ outputPath: options.dryRun ? undefined : outputPath,
492
+ spec: specYaml,
493
+ docdexId,
494
+ warnings,
495
+ };
496
+ }
497
+ catch (error) {
498
+ await this.jobService.updateJobStatus(job.id, "failed", { errorSummary: error.message });
499
+ await this.jobService.finishCommandRun(commandRun.id, "failed", error.message);
500
+ throw error;
501
+ }
502
+ }
503
+ }
@@ -0,0 +1,68 @@
1
+ import { AgentService } from "@mcoda/agents";
2
+ import { EpicRow, GlobalRepository, StoryRow, TaskDependencyRow, TaskRow, WorkspaceRepository } from "@mcoda/db";
3
+ import { DocdexClient } from "@mcoda/integrations";
4
+ import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
5
+ import { JobService } from "../jobs/JobService.js";
6
+ import { RoutingService } from "../agents/RoutingService.js";
7
+ export interface CreateTasksOptions {
8
+ workspace: WorkspaceResolution;
9
+ projectKey: string;
10
+ inputs: string[];
11
+ agentName?: string;
12
+ agentStream?: boolean;
13
+ maxEpics?: number;
14
+ maxStoriesPerEpic?: number;
15
+ maxTasksPerStory?: number;
16
+ force?: boolean;
17
+ }
18
+ export interface CreateTasksResult {
19
+ jobId: string;
20
+ commandRunId: string;
21
+ epics: EpicRow[];
22
+ stories: StoryRow[];
23
+ tasks: TaskRow[];
24
+ dependencies: TaskDependencyRow[];
25
+ }
26
+ export declare class CreateTasksService {
27
+ private static readonly MAX_BUSY_RETRIES;
28
+ private static readonly BUSY_BACKOFF_MS;
29
+ private docdex;
30
+ private jobService;
31
+ private agentService;
32
+ private repo;
33
+ private workspaceRepo;
34
+ private routingService;
35
+ private workspace;
36
+ constructor(workspace: WorkspaceResolution, deps: {
37
+ docdex: DocdexClient;
38
+ jobService: JobService;
39
+ agentService: AgentService;
40
+ repo: GlobalRepository;
41
+ workspaceRepo: WorkspaceRepository;
42
+ routingService: RoutingService;
43
+ });
44
+ static create(workspace: WorkspaceResolution): Promise<CreateTasksService>;
45
+ close(): Promise<void>;
46
+ private resolveAgent;
47
+ private prepareDocs;
48
+ private buildDocContext;
49
+ private buildPrompt;
50
+ private fallbackPlan;
51
+ private invokeAgentWithRetry;
52
+ private parseEpics;
53
+ private generateStoriesForEpic;
54
+ private generateTasksForStory;
55
+ private generatePlanFromAgent;
56
+ private writePlanArtifacts;
57
+ private persistPlanToDb;
58
+ createTasks(options: CreateTasksOptions): Promise<CreateTasksResult>;
59
+ migratePlanFromFolder(options: {
60
+ projectKey: string;
61
+ planDir?: string;
62
+ force?: boolean;
63
+ refinePlanPath?: string;
64
+ refinePlanPaths?: string[];
65
+ refinePlansDir?: string;
66
+ }): Promise<CreateTasksResult>;
67
+ }
68
+ //# sourceMappingURL=CreateTasksService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CreateTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/CreateTasksService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAEL,OAAO,EACP,gBAAgB,EAEhB,QAAQ,EAER,iBAAiB,EAEjB,OAAO,EACP,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAO7D,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,YAAY,EAAE,iBAAiB,EAAE,CAAC;CACnC;AA4QD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAO;IAC9C,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,SAAS,CAAsB;gBAGrC,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE;QACJ,MAAM,EAAE,YAAY,CAAC;QACrB,UAAU,EAAE,UAAU,CAAC;QACvB,YAAY,EAAE,YAAY,CAAC;QAC3B,IAAI,EAAE,gBAAgB,CAAC;QACvB,aAAa,EAAE,mBAAmB,CAAC;QACnC,cAAc,EAAE,cAAc,CAAC;KAChC;WAWU,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAoB1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBd,YAAY;YASZ,WAAW;IAmCzB,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,WAAW;IA+BnB,OAAO,CAAC,YAAY;YAkDN,oBAAoB;IAuElC,OAAO,CAAC,UAAU;YAmBJ,sBAAsB;YA0CtB,qBAAqB;YA6CrB,qBAAqB;YAyDrB,kBAAkB;YAkBlB,eAAe;IA4MvB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAwIpE,qBAAqB,CAAC,OAAO,EAAE;QACnC,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA6I/B"}