@mcoda/core 0.1.8 → 0.1.9

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 (70) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/api/AgentsApi.d.ts +8 -1
  3. package/dist/api/AgentsApi.d.ts.map +1 -1
  4. package/dist/api/AgentsApi.js +70 -0
  5. package/dist/api/QaTasksApi.d.ts.map +1 -1
  6. package/dist/api/QaTasksApi.js +2 -0
  7. package/dist/api/TasksApi.d.ts.map +1 -1
  8. package/dist/api/TasksApi.js +1 -0
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -0
  12. package/dist/prompts/PdrPrompts.d.ts.map +1 -1
  13. package/dist/prompts/PdrPrompts.js +3 -1
  14. package/dist/prompts/SdsPrompts.d.ts.map +1 -1
  15. package/dist/prompts/SdsPrompts.js +2 -0
  16. package/dist/services/agents/AgentRatingFormula.d.ts +27 -0
  17. package/dist/services/agents/AgentRatingFormula.d.ts.map +1 -0
  18. package/dist/services/agents/AgentRatingFormula.js +45 -0
  19. package/dist/services/agents/AgentRatingService.d.ts +41 -0
  20. package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
  21. package/dist/services/agents/AgentRatingService.js +299 -0
  22. package/dist/services/agents/GatewayAgentService.d.ts +3 -0
  23. package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
  24. package/dist/services/agents/GatewayAgentService.js +68 -24
  25. package/dist/services/agents/GatewayHandoff.d.ts +7 -0
  26. package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
  27. package/dist/services/agents/GatewayHandoff.js +108 -0
  28. package/dist/services/backlog/TaskOrderingService.d.ts +1 -0
  29. package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
  30. package/dist/services/backlog/TaskOrderingService.js +19 -16
  31. package/dist/services/docs/DocsService.d.ts +11 -1
  32. package/dist/services/docs/DocsService.d.ts.map +1 -1
  33. package/dist/services/docs/DocsService.js +240 -52
  34. package/dist/services/execution/GatewayTrioService.d.ts +133 -0
  35. package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
  36. package/dist/services/execution/GatewayTrioService.js +1125 -0
  37. package/dist/services/execution/QaFollowupService.d.ts +1 -0
  38. package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
  39. package/dist/services/execution/QaFollowupService.js +1 -0
  40. package/dist/services/execution/QaProfileService.d.ts +6 -0
  41. package/dist/services/execution/QaProfileService.d.ts.map +1 -1
  42. package/dist/services/execution/QaProfileService.js +165 -3
  43. package/dist/services/execution/QaTasksService.d.ts +18 -0
  44. package/dist/services/execution/QaTasksService.d.ts.map +1 -1
  45. package/dist/services/execution/QaTasksService.js +712 -34
  46. package/dist/services/execution/WorkOnTasksService.d.ts +14 -0
  47. package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
  48. package/dist/services/execution/WorkOnTasksService.js +1497 -240
  49. package/dist/services/openapi/OpenApiService.d.ts +10 -0
  50. package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
  51. package/dist/services/openapi/OpenApiService.js +66 -10
  52. package/dist/services/planning/CreateTasksService.d.ts +6 -0
  53. package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
  54. package/dist/services/planning/CreateTasksService.js +261 -28
  55. package/dist/services/planning/RefineTasksService.d.ts +5 -0
  56. package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
  57. package/dist/services/planning/RefineTasksService.js +184 -35
  58. package/dist/services/review/CodeReviewService.d.ts +14 -0
  59. package/dist/services/review/CodeReviewService.d.ts.map +1 -1
  60. package/dist/services/review/CodeReviewService.js +657 -61
  61. package/dist/services/shared/ProjectGuidance.d.ts +6 -0
  62. package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
  63. package/dist/services/shared/ProjectGuidance.js +21 -0
  64. package/dist/services/tasks/TaskCommentFormatter.d.ts +20 -0
  65. package/dist/services/tasks/TaskCommentFormatter.d.ts.map +1 -0
  66. package/dist/services/tasks/TaskCommentFormatter.js +54 -0
  67. package/dist/workspace/WorkspaceManager.d.ts +4 -0
  68. package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
  69. package/dist/workspace/WorkspaceManager.js +3 -0
  70. package/package.json +5 -5
@@ -522,23 +522,26 @@ export class TaskOrderingService {
522
522
  warnings.push("Dependency cycle detected; ordering may be partial.");
523
523
  }
524
524
  let agentRank;
525
- const docContext = await this.buildDocContext(project.key, warnings);
526
- if (docContext && commandRun && this.recordTelemetry) {
527
- const contextTokens = estimateTokens(docContext.content);
528
- await this.jobService.recordTokenUsage({
529
- workspaceId: this.workspace.workspaceId,
530
- projectId: project.id,
531
- commandRunId: commandRun.id,
532
- jobId: job?.id,
533
- timestamp: new Date().toISOString(),
534
- commandName: "order-tasks",
535
- action: "docdex_context",
536
- tokensPrompt: contextTokens,
537
- tokensTotal: contextTokens,
538
- metadata: { source: docContext.source },
539
- });
525
+ const enableAgent = Boolean(request.agentName);
526
+ let docContext;
527
+ if (enableAgent) {
528
+ docContext = await this.buildDocContext(project.key, warnings);
529
+ if (docContext && commandRun && this.recordTelemetry) {
530
+ const contextTokens = estimateTokens(docContext.content);
531
+ await this.jobService.recordTokenUsage({
532
+ workspaceId: this.workspace.workspaceId,
533
+ projectId: project.id,
534
+ commandRunId: commandRun.id,
535
+ jobId: job?.id,
536
+ timestamp: new Date().toISOString(),
537
+ commandName: "order-tasks",
538
+ action: "docdex_context",
539
+ tokensPrompt: contextTokens,
540
+ tokensTotal: contextTokens,
541
+ metadata: { source: docContext.source },
542
+ });
543
+ }
540
544
  }
541
- const enableAgent = request.agentName !== undefined || this.recordTelemetry;
542
545
  if (enableAgent) {
543
546
  try {
544
547
  const agent = await this.resolveAgent(request.agentName);
@@ -1,9 +1,10 @@
1
1
  import { AgentService } from "@mcoda/agents";
2
- import { GlobalRepository } from "@mcoda/db";
2
+ import { GlobalRepository, WorkspaceRepository } from "@mcoda/db";
3
3
  import { DocdexClient } from "@mcoda/integrations";
4
4
  import { JobService } from "../jobs/JobService.js";
5
5
  import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
6
6
  import { RoutingService } from "../agents/RoutingService.js";
7
+ import { AgentRatingService } from "../agents/AgentRatingService.js";
7
8
  export interface GeneratePdrOptions {
8
9
  workspace: WorkspaceResolution;
9
10
  projectKey?: string;
@@ -12,6 +13,8 @@ export interface GeneratePdrOptions {
12
13
  outPath?: string;
13
14
  agentName?: string;
14
15
  agentStream?: boolean;
16
+ rateAgents?: boolean;
17
+ fast?: boolean;
15
18
  dryRun?: boolean;
16
19
  json?: boolean;
17
20
  onToken?: (token: string) => void;
@@ -32,6 +35,8 @@ export interface GenerateSdsOptions {
32
35
  agentName?: string;
33
36
  templateName?: string;
34
37
  agentStream?: boolean;
38
+ rateAgents?: boolean;
39
+ fast?: boolean;
35
40
  dryRun?: boolean;
36
41
  json?: boolean;
37
42
  force?: boolean;
@@ -53,12 +58,16 @@ export declare class DocsService {
53
58
  private agentService;
54
59
  private repo;
55
60
  private routingService;
61
+ private ratingService?;
62
+ private workspaceRepo?;
56
63
  constructor(workspace: WorkspaceResolution, deps: {
57
64
  docdex?: DocdexClient;
58
65
  jobService?: JobService;
59
66
  agentService: AgentService;
60
67
  repo: GlobalRepository;
61
68
  routingService: RoutingService;
69
+ workspaceRepo?: WorkspaceRepository;
70
+ ratingService?: AgentRatingService;
62
71
  noTelemetry?: boolean;
63
72
  });
64
73
  static create(workspace: WorkspaceResolution, options?: {
@@ -69,6 +78,7 @@ export declare class DocsService {
69
78
  private defaultSdsOutputPath;
70
79
  private loadSdsTemplate;
71
80
  private resolveAgent;
81
+ private ensureRatingService;
72
82
  private writePdrFile;
73
83
  private registerPdr;
74
84
  private writeSdsFile;
@@ -1 +1 @@
1
- {"version":3,"file":"DocsService.d.ts","sourceRoot":"","sources":["../../../src/services/docs/DocsService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DocsService.d.ts","sourceRoot":"","sources":["../../../src/services/docs/DocsService.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,13 @@
1
1
  import path from "node:path";
2
2
  import { promises as fs } from "node:fs";
3
3
  import { AgentService } from "@mcoda/agents";
4
- import { GlobalRepository } from "@mcoda/db";
4
+ import { GlobalRepository, WorkspaceRepository } from "@mcoda/db";
5
5
  import { DocdexClient } from "@mcoda/integrations";
6
6
  import { DEFAULT_PDR_CHARACTER_PROMPT, DEFAULT_PDR_JOB_PROMPT, DEFAULT_PDR_RUNBOOK_PROMPT, } from "../../prompts/PdrPrompts.js";
7
7
  import { DEFAULT_SDS_CHARACTER_PROMPT, DEFAULT_SDS_JOB_PROMPT, DEFAULT_SDS_RUNBOOK_PROMPT, DEFAULT_SDS_TEMPLATE, } from "../../prompts/SdsPrompts.js";
8
8
  import { JobService } from "../jobs/JobService.js";
9
9
  import { RoutingService } from "../agents/RoutingService.js";
10
+ import { AgentRatingService } from "../agents/AgentRatingService.js";
10
11
  const ensureDir = async (targetPath) => {
11
12
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
12
13
  };
@@ -22,6 +23,7 @@ const readPromptIfExists = async (workspace, relative) => {
22
23
  const PDR_REQUIRED_HEADINGS = [
23
24
  ["Introduction"],
24
25
  ["Scope"],
26
+ ["Technology Stack", "Tech Stack"],
25
27
  ["Requirements", "Requirements & Constraints"],
26
28
  ["Architecture", "Architecture Overview"],
27
29
  ["Interfaces", "Interfaces / APIs"],
@@ -88,6 +90,43 @@ const extractBullets = (content, limit = 20) => {
88
90
  .filter((line) => line.length > 0)
89
91
  .slice(0, limit);
90
92
  };
93
+ const DEFAULT_TECH_STACK_FALLBACK = [
94
+ "- Frontend: React + TypeScript",
95
+ "- Backend/services: TypeScript (Node.js)",
96
+ "- Database: MySQL",
97
+ "- Cache/queues: Redis",
98
+ "- Scripting/ops: Bash",
99
+ "- Override defaults if the RFP specifies a different stack.",
100
+ ].join("\n");
101
+ const ML_TECH_STACK_FALLBACK = [
102
+ "- Language: Python",
103
+ "- ML stack: PyTorch or TensorFlow (pick based on model requirements)",
104
+ "- Services/API: Python web framework (FastAPI/Flask) as needed",
105
+ "- Database: MySQL",
106
+ "- Cache/queues: Redis",
107
+ "- Scripting/ops: Bash",
108
+ "- Override defaults if the RFP specifies a different stack.",
109
+ ].join("\n");
110
+ const contextIndicatesMlStack = (context) => {
111
+ const sources = [context.rfp?.content, ...context.related.map((doc) => doc.content ?? "")].filter(Boolean);
112
+ if (!sources.length)
113
+ return false;
114
+ const text = sources.join("\n").toLowerCase();
115
+ const patterns = [
116
+ /\bmachine learning\b/,
117
+ /\bdeep learning\b/,
118
+ /\bneural\b/,
119
+ /\bmodel training\b/,
120
+ /\bmodel inference\b/,
121
+ /\bml\b/,
122
+ /\bllm\b/,
123
+ /\bembeddings?\b/,
124
+ ];
125
+ return patterns.some((pattern) => pattern.test(text));
126
+ };
127
+ const resolveTechStackFallback = (context) => {
128
+ return contextIndicatesMlStack(context) ? ML_TECH_STACK_FALLBACK : DEFAULT_TECH_STACK_FALLBACK;
129
+ };
91
130
  class DocContextAssembler {
92
131
  constructor(docdex, workspace) {
93
132
  this.docdex = docdex;
@@ -330,10 +369,22 @@ class DocContextAssembler {
330
369
  }
331
370
  let related = [];
332
371
  if (docdexAvailable) {
333
- related = await this.docdex.search({ projectKey: input.projectKey, docType: "PDR", profile: "rfp_default" });
334
- const sds = await this.docdex.search({ projectKey: input.projectKey, docType: "SDS", profile: "rfp_default" });
335
- openapi = await this.docdex.search({ projectKey: input.projectKey, docType: "OPENAPI", profile: "rfp_default" });
336
- related = [...related, ...sds];
372
+ try {
373
+ related = await this.docdex.search({ projectKey: input.projectKey, docType: "PDR", profile: "rfp_default" });
374
+ const sds = await this.docdex.search({ projectKey: input.projectKey, docType: "SDS", profile: "rfp_default" });
375
+ openapi = await this.docdex.search({
376
+ projectKey: input.projectKey,
377
+ docType: "OPENAPI",
378
+ profile: "rfp_default",
379
+ });
380
+ related = [...related, ...sds];
381
+ }
382
+ catch (error) {
383
+ docdexAvailable = false;
384
+ related = [];
385
+ openapi = [];
386
+ warnings.push(`Docdex unavailable; continuing without related docs (${error.message ?? "unknown error"}).`);
387
+ }
337
388
  }
338
389
  const summaryParts = [
339
390
  `RFP: ${this.summarize(rfp)}`,
@@ -383,7 +434,7 @@ const buildRunPrompt = (context, projectKey, prompts, runbook) => {
383
434
  docdexNote,
384
435
  [
385
436
  "Return markdown with exactly these sections as H2 headings, one time each:",
386
- "Introduction, Scope, Requirements & Constraints, Architecture Overview, Interfaces / APIs, Non-Functional Requirements, Risks & Mitigations, Open Questions, Acceptance Criteria",
437
+ "Introduction, Scope, Technology Stack, Requirements & Constraints, Architecture Overview, Interfaces / APIs, Non-Functional Requirements, Risks & Mitigations, Open Questions, Acceptance Criteria",
387
438
  "Do not use bold headings; use `##` headings only. Do not repeat sections.",
388
439
  ].join("\n"),
389
440
  runbookPrompt,
@@ -421,12 +472,14 @@ const buildSdsRunPrompt = (context, projectKey, prompts, runbook, template) => {
421
472
  const ensureStructuredDraft = (draft, projectKey, context, rfpSource) => {
422
473
  const canonicalTitles = PDR_REQUIRED_HEADINGS.map((variants) => variants[0]);
423
474
  const normalized = normalizeHeadingsToH2(draft, canonicalTitles);
475
+ const techStackFallback = resolveTechStackFallback(context);
424
476
  const required = [
425
477
  { title: "Introduction", fallback: `This PDR summarizes project ${projectKey ?? "N/A"} based on ${rfpSource}.` },
426
478
  {
427
479
  title: "Scope",
428
480
  fallback: "In-scope: todo CRUD (title required; optional description, due date, priority), status toggle, filters/sort/search, bulk complete/delete, keyboard shortcuts, responsive UI, offline/localStorage. Out-of-scope: multi-user/auth/sync/backends, notifications/reminders, team features, heavy UI kits.",
429
481
  },
482
+ { title: "Technology Stack", fallback: techStackFallback },
430
483
  {
431
484
  title: "Requirements & Constraints",
432
485
  fallback: context.bullets.map((b) => `- ${b}`).join("\n") ||
@@ -444,7 +497,20 @@ const ensureStructuredDraft = (draft, projectKey, context, rfpSource) => {
444
497
  for (const section of required) {
445
498
  const best = getBestSectionBody(normalized, section.title);
446
499
  const cleaned = cleanBody(best ?? "");
447
- const body = cleaned && cleaned.length > 0 ? cleaned : cleanBody(section.fallback);
500
+ let body = cleaned && cleaned.length > 0 ? cleaned : cleanBody(section.fallback);
501
+ if (section.title === "Interfaces / APIs" && (context.openapi?.length ?? 0) === 0) {
502
+ const scrubbed = stripInventedEndpoints(body);
503
+ const openApiFallback = "No OpenAPI excerpts available. Capture interface needs as open questions (auth/identity, restaurant suggestions, voting cycles, results/analytics).";
504
+ if (!scrubbed || scrubbed.length === 0 || /endpoint/i.test(scrubbed)) {
505
+ body = cleanBody(openApiFallback);
506
+ }
507
+ else {
508
+ body = scrubbed;
509
+ }
510
+ if (!/openapi/i.test(body)) {
511
+ body = `${body}\n- No OpenAPI excerpts available; keep endpoints as open questions.`;
512
+ }
513
+ }
448
514
  parts.push(`## ${section.title}`);
449
515
  parts.push(body);
450
516
  }
@@ -458,7 +524,7 @@ const tidyPdrDraft = async (draft, agent, invoke) => {
458
524
  draft,
459
525
  "",
460
526
  "Requirements:",
461
- "- Keep exactly one instance of each H2 section: Introduction, Scope, Requirements & Constraints, Architecture Overview, Interfaces / APIs, Non-Functional Requirements, Risks & Mitigations, Open Questions, Acceptance Criteria, Source RFP.",
527
+ "- Keep exactly one instance of each H2 section: Introduction, Scope, Technology Stack, Requirements & Constraints, Architecture Overview, Interfaces / APIs, Non-Functional Requirements, Risks & Mitigations, Open Questions, Acceptance Criteria, Source RFP.",
462
528
  "- Remove duplicate sections, bold headings posing as sections, placeholder sentences, and repeated bullet blocks. If the same idea appears twice, keep the richer/longer version and drop the restatement.",
463
529
  "- Do not add new sections or reorder the required outline.",
464
530
  "- Keep content concise and aligned to the headings. Do not alter semantics.",
@@ -468,6 +534,13 @@ const tidyPdrDraft = async (draft, agent, invoke) => {
468
534
  return output.trim();
469
535
  };
470
536
  const PDR_ENRICHMENT_SECTIONS = [
537
+ {
538
+ title: "Technology Stack",
539
+ guidance: [
540
+ "List frontend, backend/services, databases, caches/queues, infra/runtime, and scripting/tooling choices.",
541
+ "If the RFP omits stack details, state the default stack (TypeScript/React/MySQL/Redis/Bash) or a Python ML stack when neural/ML workloads are explicit.",
542
+ ],
543
+ },
471
544
  {
472
545
  title: "Architecture Overview",
473
546
  guidance: [
@@ -588,6 +661,19 @@ const ensureSdsStructuredDraft = (draft, projectKey, context, template) => {
588
661
  for (const section of sections) {
589
662
  structured = ensureSectionContent(structured, section, fallbackFor(section));
590
663
  }
664
+ if ((context.openapi?.length ?? 0) === 0) {
665
+ const interfaceTitle = sections.find((section) => /interface|contract/i.test(section)) ?? "Interfaces & Contracts";
666
+ const extracted = extractSection(structured, interfaceTitle);
667
+ if (extracted) {
668
+ const scrubbed = stripInventedEndpoints(cleanBody(extracted.body ?? ""));
669
+ const openApiFallback = "No OpenAPI excerpts available. Capture interface needs as open questions (auth/identity, restaurant suggestions, voting cycles, results/analytics).";
670
+ let body = scrubbed.length > 0 && !/endpoint/i.test(scrubbed) ? scrubbed : cleanBody(openApiFallback);
671
+ if (!/openapi/i.test(body)) {
672
+ body = `${body}\n- No OpenAPI excerpts available; keep endpoints as open questions.`;
673
+ }
674
+ structured = replaceSection(structured, interfaceTitle, body);
675
+ }
676
+ }
591
677
  return structured;
592
678
  };
593
679
  const getSdsSections = (template) => {
@@ -847,6 +933,7 @@ const PLACEHOLDER_PATTERNS = [
847
933
  /^[-*+.]?\s*Outstanding questions/i,
848
934
  /^[-*+.]?\s*Performance, reliability, compliance/i,
849
935
  /^[-*+.]?\s*Enumerate risks from the RFP/i,
936
+ /^I (will|am going to|plan to|am)\s+(read|review|analy[sz]e|gather|start|begin|look|scan)\b/i,
850
937
  ];
851
938
  const cleanBody = (body) => {
852
939
  const requiredTitles = PDR_REQUIRED_HEADINGS.flat().map((t) => t.toLowerCase());
@@ -887,6 +974,14 @@ const cleanBody = (body) => {
887
974
  }
888
975
  return deduped.join("\n").trim();
889
976
  };
977
+ const stripInventedEndpoints = (body) => {
978
+ const lines = body.split(/\r?\n/);
979
+ const filtered = lines.filter((line) => {
980
+ const normalized = line.replace(/[`*_]/g, "");
981
+ return !/(^|\s)\b(GET|POST|PUT|PATCH|DELETE)\b[^\n]*\//i.test(normalized);
982
+ });
983
+ return filtered.join("\n").trim();
984
+ };
890
985
  const extractSection = (draft, title) => {
891
986
  const regex = new RegExp(`(^#{1,6}\\s+${title}\\b)([\\s\\S]*?)(?=^#{1,6}\\s+|(?![\\s\\S]))`, "im");
892
987
  const match = draft.match(regex);
@@ -910,7 +1005,7 @@ const getBestSectionBody = (draft, title) => {
910
1005
  };
911
1006
  const replaceSection = (draft, title, newBody) => {
912
1007
  const normalizedBody = cleanBody(newBody);
913
- const regex = new RegExp(`(^#{1,6}\\s+${title}\\b)([\\s\\S]*?)(?=^#{1,6}\\s+|$)`, "im");
1008
+ const regex = new RegExp(`(^#{1,6}\\s+${title}\\b)([\\s\\S]*?)(?=^#{1,6}\\s+|(?![\\s\\S]))`, "im");
914
1009
  if (regex.test(draft)) {
915
1010
  return draft.replace(regex, `$1\n\n${normalizedBody}\n\n`);
916
1011
  }
@@ -983,6 +1078,8 @@ export class DocsService {
983
1078
  this.repo = deps.repo;
984
1079
  this.agentService = deps.agentService;
985
1080
  this.routingService = deps.routingService;
1081
+ this.workspaceRepo = deps.workspaceRepo;
1082
+ this.ratingService = deps.ratingService;
986
1083
  }
987
1084
  static async create(workspace, options = {}) {
988
1085
  const repo = await GlobalRepository.create();
@@ -996,15 +1093,19 @@ export class DocsService {
996
1093
  return new DocsService(workspace, { repo, agentService, routingService, docdex, jobService, noTelemetry: options.noTelemetry });
997
1094
  }
998
1095
  async close() {
999
- if (this.agentService.close) {
1000
- await this.agentService.close();
1001
- }
1002
- if (this.repo.close) {
1003
- await this.repo.close();
1004
- }
1005
- if (this.jobService.close) {
1006
- await this.jobService.close();
1007
- }
1096
+ const swallow = async (fn) => {
1097
+ try {
1098
+ if (fn)
1099
+ await fn();
1100
+ }
1101
+ catch {
1102
+ // Best-effort close; ignore errors (including "database is closed").
1103
+ }
1104
+ };
1105
+ await swallow(this.agentService.close?.bind(this.agentService));
1106
+ await swallow(this.repo.close?.bind(this.repo));
1107
+ await swallow(this.jobService.close?.bind(this.jobService));
1108
+ await swallow(this.workspaceRepo?.close?.bind(this.workspaceRepo));
1008
1109
  }
1009
1110
  defaultPdrOutputPath(projectKey, rfpPath) {
1010
1111
  const slug = slugify(projectKey ?? (rfpPath ? path.basename(rfpPath, path.extname(rfpPath)) : "pdr"));
@@ -1045,6 +1146,23 @@ export class DocsService {
1045
1146
  });
1046
1147
  return resolved.agent;
1047
1148
  }
1149
+ async ensureRatingService() {
1150
+ if (this.ratingService)
1151
+ return this.ratingService;
1152
+ if (process.env.MCODA_DISABLE_DB === "1") {
1153
+ throw new Error("Workspace DB disabled; agent rating requires DB access.");
1154
+ }
1155
+ if (!this.workspaceRepo) {
1156
+ this.workspaceRepo = await WorkspaceRepository.create(this.workspace.workspaceRoot);
1157
+ }
1158
+ this.ratingService = new AgentRatingService(this.workspace, {
1159
+ workspaceRepo: this.workspaceRepo,
1160
+ globalRepo: this.repo,
1161
+ agentService: this.agentService,
1162
+ routingService: this.routingService,
1163
+ });
1164
+ return this.ratingService;
1165
+ }
1048
1166
  async writePdrFile(outPath, content) {
1049
1167
  await ensureDir(outPath);
1050
1168
  await fs.writeFile(outPath, content, "utf8");
@@ -1163,6 +1281,7 @@ export class DocsService {
1163
1281
  workspaceId: this.workspace.workspaceId,
1164
1282
  commandName: "docs-pdr-generate",
1165
1283
  jobId: job.id,
1284
+ commandRunId: commandRun.id,
1166
1285
  action: "docdex_context",
1167
1286
  promptTokens: 0,
1168
1287
  completionTokens: 0,
@@ -1173,14 +1292,17 @@ export class DocsService {
1173
1292
  const runbook = (await readPromptIfExists(this.workspace, path.join("prompts", "commands", "pdr-generate.md"))) ||
1174
1293
  DEFAULT_PDR_RUNBOOK_PROMPT;
1175
1294
  let draft = "";
1295
+ let agentUsed = false;
1176
1296
  let agentMetadata;
1177
1297
  let adapter = agent.adapter;
1178
1298
  const stream = options.agentStream ?? true;
1299
+ const fastMode = options.fast === true || process.env.MCODA_DOCS_FAST === "1";
1179
1300
  const skipValidation = process.env.MCODA_SKIP_PDR_VALIDATION === "1";
1180
1301
  let lastInvoke;
1181
1302
  for (let attempt = 0; attempt < 2; attempt += 1) {
1182
1303
  const prompt = buildRunPrompt(context, options.projectKey, prompts, attempt === 0 ? runbook : `${runbook}\n\nRETRY: The previous attempt failed validation. Ensure all required sections are present and non-empty. Do not leave placeholders.`);
1183
1304
  const invoke = async (input) => {
1305
+ agentUsed = true;
1184
1306
  const { output: out, adapter: usedAdapter, metadata } = await this.invokeAgent(agent, input, stream, job.id, options.onToken);
1185
1307
  adapter = usedAdapter;
1186
1308
  agentMetadata = metadata;
@@ -1195,6 +1317,7 @@ export class DocsService {
1195
1317
  workspaceId: this.workspace.workspaceId,
1196
1318
  commandName: "docs-pdr-generate",
1197
1319
  jobId: job.id,
1320
+ commandRunId: commandRun.id,
1198
1321
  agentId: agent.id,
1199
1322
  modelName: agent.defaultModel,
1200
1323
  action: attempt === 0 ? "draft_pdr" : "draft_pdr_retry",
@@ -1218,11 +1341,14 @@ export class DocsService {
1218
1341
  if (!draft) {
1219
1342
  throw new Error("PDR draft generation failed; no valid draft produced.");
1220
1343
  }
1221
- if (lastInvoke) {
1344
+ if (fastMode) {
1345
+ context.warnings.push("Fast mode enabled; skipping PDR enrichment and tidy passes.");
1346
+ }
1347
+ else if (lastInvoke) {
1222
1348
  draft = await enrichPdrDraft(draft, agent, context, options.projectKey, lastInvoke);
1223
1349
  draft = ensureStructuredDraft(draft, options.projectKey, context, context.rfp.path ?? context.rfp.id ?? "RFP");
1224
1350
  }
1225
- if (lastInvoke) {
1351
+ if (!fastMode && lastInvoke) {
1226
1352
  try {
1227
1353
  const tidiedRaw = await tidyPdrDraft(draft, agent, lastInvoke);
1228
1354
  const tidied = ensureStructuredDraft(tidiedRaw, options.projectKey, context, context.rfp.path ?? context.rfp.id ?? "RFP");
@@ -1241,12 +1367,17 @@ export class DocsService {
1241
1367
  const firstDraftPath = path.join(this.workspace.mcodaDir, "docs", "pdr", `${path.basename(outputPath, path.extname(outputPath))}-first-draft.md`);
1242
1368
  await ensureDir(firstDraftPath);
1243
1369
  await fs.writeFile(firstDraftPath, draft, "utf8");
1244
- try {
1245
- const iterativeDraft = await buildIterativePdr(options.projectKey, context, draft, outputPath, lastInvoke ?? (async (input) => this.invokeAgent(agent, input, stream, job.id, options.onToken)));
1246
- draft = iterativeDraft;
1370
+ if (fastMode) {
1371
+ context.warnings.push("Fast mode enabled; skipping iterative PDR refinement.");
1247
1372
  }
1248
- catch (error) {
1249
- context.warnings.push(`Iterative PDR refinement failed; keeping first draft. ${String(error)}`);
1373
+ else {
1374
+ try {
1375
+ const iterativeDraft = await buildIterativePdr(options.projectKey, context, draft, outputPath, lastInvoke ?? (async (input) => this.invokeAgent(agent, input, stream, job.id, options.onToken)));
1376
+ draft = iterativeDraft;
1377
+ }
1378
+ catch (error) {
1379
+ context.warnings.push(`Iterative PDR refinement failed; keeping first draft. ${String(error)}`);
1380
+ }
1250
1381
  }
1251
1382
  }
1252
1383
  await this.jobService.writeCheckpoint(job.id, {
@@ -1263,10 +1394,15 @@ export class DocsService {
1263
1394
  if (!options.dryRun) {
1264
1395
  await this.writePdrFile(outputPath, draft);
1265
1396
  if (context.docdexAvailable) {
1266
- const registered = await this.registerPdr(outputPath, draft, options.projectKey);
1267
- docdexId = registered.id;
1268
- segments = (registered.segments ?? []).map((s) => s.id);
1269
- await fs.writeFile(`${outputPath}.meta.json`, JSON.stringify({ docdexId, segments, projectKey: options.projectKey }, null, 2), "utf8");
1397
+ try {
1398
+ const registered = await this.registerPdr(outputPath, draft, options.projectKey);
1399
+ docdexId = registered.id;
1400
+ segments = (registered.segments ?? []).map((s) => s.id);
1401
+ await fs.writeFile(`${outputPath}.meta.json`, JSON.stringify({ docdexId, segments, projectKey: options.projectKey }, null, 2), "utf8");
1402
+ }
1403
+ catch (error) {
1404
+ context.warnings.push(`Docdex registration skipped: ${error.message}`);
1405
+ }
1270
1406
  }
1271
1407
  const publicDocsDir = path.join(this.workspace.workspaceRoot, "docs", "pdr");
1272
1408
  const shouldMirror = this.workspace.config?.mirrorDocs !== false;
@@ -1288,6 +1424,21 @@ export class DocsService {
1288
1424
  payload: { outputPath, docdexId, segments, mirrorStatus },
1289
1425
  });
1290
1426
  await this.jobService.finishCommandRun(commandRun.id, "succeeded");
1427
+ if (options.rateAgents && agentUsed) {
1428
+ try {
1429
+ const ratingService = await this.ensureRatingService();
1430
+ await ratingService.rate({
1431
+ workspace: this.workspace,
1432
+ agentId: agent.id,
1433
+ commandName: "docs-pdr-generate",
1434
+ jobId: job.id,
1435
+ commandRunId: commandRun.id,
1436
+ });
1437
+ }
1438
+ catch (error) {
1439
+ context.warnings.push(`Agent rating failed: ${error.message ?? String(error)}`);
1440
+ }
1441
+ }
1291
1442
  return {
1292
1443
  jobId: job.id,
1293
1444
  commandRunId: commandRun.id,
@@ -1423,6 +1574,7 @@ export class DocsService {
1423
1574
  workspaceId: this.workspace.workspaceId,
1424
1575
  commandName: "docs-sds-generate",
1425
1576
  jobId: job.id,
1577
+ commandRunId: commandRun.id,
1426
1578
  action: "docdex_context",
1427
1579
  promptTokens: 0,
1428
1580
  completionTokens: 0,
@@ -1452,11 +1604,14 @@ export class DocsService {
1452
1604
  (await readPromptIfExists(this.workspace, path.join("prompts", "sds", "generate.md"))) ||
1453
1605
  DEFAULT_SDS_RUNBOOK_PROMPT;
1454
1606
  let draft = resumeDraft ?? "";
1607
+ let agentUsed = false;
1455
1608
  let agentMetadata;
1456
1609
  let adapter = agent.adapter;
1457
1610
  const stream = options.agentStream ?? true;
1611
+ const fastMode = options.fast === true || process.env.MCODA_DOCS_FAST === "1";
1458
1612
  const skipValidation = process.env.MCODA_SKIP_SDS_VALIDATION === "1";
1459
1613
  const invoke = async (input) => {
1614
+ agentUsed = true;
1460
1615
  const { output: out, adapter: usedAdapter, metadata } = await this.invokeAgent(agent, input, stream, job.id, options.onToken);
1461
1616
  adapter = usedAdapter;
1462
1617
  if (metadata)
@@ -1476,6 +1631,7 @@ export class DocsService {
1476
1631
  workspaceId: this.workspace.workspaceId,
1477
1632
  commandName: "docs-sds-generate",
1478
1633
  jobId: job.id,
1634
+ commandRunId: commandRun.id,
1479
1635
  agentId: agent.id,
1480
1636
  modelName: agent.defaultModel,
1481
1637
  action: attempt === 0 ? "draft_sds" : "draft_sds_retry",
@@ -1508,6 +1664,7 @@ export class DocsService {
1508
1664
  workspaceId: this.workspace.workspaceId,
1509
1665
  commandName: "docs-sds-generate",
1510
1666
  jobId: job.id,
1667
+ commandRunId: commandRun.id,
1511
1668
  action: "draft_sds_resume",
1512
1669
  promptTokens: 0,
1513
1670
  completionTokens: estimateTokens(draft),
@@ -1531,6 +1688,7 @@ export class DocsService {
1531
1688
  workspaceId: this.workspace.workspaceId,
1532
1689
  commandName: "docs-sds-generate",
1533
1690
  jobId: job.id,
1691
+ commandRunId: commandRun.id,
1534
1692
  agentId: agent.id,
1535
1693
  modelName: agent.defaultModel,
1536
1694
  action: "draft_sds_resume_regenerate",
@@ -1539,34 +1697,44 @@ export class DocsService {
1539
1697
  metadata: { adapter, provider: adapter, docdexAvailable: context.docdexAvailable, template: template.name },
1540
1698
  });
1541
1699
  }
1542
- // Enrich each section sequentially after a valid base draft exists.
1543
- draft = await enrichSdsDraft(draft, sdsSections, agent, context, options.projectKey, invoke);
1544
- draft = ensureSdsStructuredDraft(draft, options.projectKey, context, template.content);
1545
- if (!skipValidation && !(validateSdsDraft(draft) && headingHasContent(draft, "Architecture"))) {
1546
- warnings.push("Enriched SDS draft failed validation; using structured fallback.");
1547
- draft = ensureSdsStructuredDraft(draft, options.projectKey, context, template.content);
1700
+ if (fastMode) {
1701
+ warnings.push("Fast mode enabled; skipping SDS enrichment and tidy passes.");
1548
1702
  }
1549
- try {
1550
- const tidiedRaw = await tidySdsDraft(draft, sdsSections, agent, invoke);
1551
- const tidied = ensureSdsStructuredDraft(tidiedRaw, options.projectKey, context, template.content);
1552
- if (skipValidation || (validateSdsDraft(tidied) && headingHasContent(tidied, "Architecture"))) {
1553
- draft = tidied;
1703
+ else {
1704
+ // Enrich each section sequentially after a valid base draft exists.
1705
+ draft = await enrichSdsDraft(draft, sdsSections, agent, context, options.projectKey, invoke);
1706
+ draft = ensureSdsStructuredDraft(draft, options.projectKey, context, template.content);
1707
+ if (!skipValidation && !(validateSdsDraft(draft) && headingHasContent(draft, "Architecture"))) {
1708
+ warnings.push("Enriched SDS draft failed validation; using structured fallback.");
1709
+ draft = ensureSdsStructuredDraft(draft, options.projectKey, context, template.content);
1710
+ }
1711
+ try {
1712
+ const tidiedRaw = await tidySdsDraft(draft, sdsSections, agent, invoke);
1713
+ const tidied = ensureSdsStructuredDraft(tidiedRaw, options.projectKey, context, template.content);
1714
+ if (skipValidation || (validateSdsDraft(tidied) && headingHasContent(tidied, "Architecture"))) {
1715
+ draft = tidied;
1716
+ }
1717
+ }
1718
+ catch (error) {
1719
+ warnings.push(`SDS tidy pass skipped: ${error.message ?? "unknown error"}`);
1554
1720
  }
1555
- }
1556
- catch (error) {
1557
- warnings.push(`SDS tidy pass skipped: ${error.message ?? "unknown error"}`);
1558
1721
  }
1559
1722
  await fs.mkdir(path.dirname(draftPath), { recursive: true });
1560
1723
  await fs.writeFile(draftPath, draft, "utf8");
1561
1724
  const firstDraftPath = path.join(this.workspace.mcodaDir, "docs", "sds", `${path.basename(outputPath, path.extname(outputPath))}-first-draft.md`);
1562
1725
  await ensureDir(firstDraftPath);
1563
1726
  await fs.writeFile(firstDraftPath, draft, "utf8");
1564
- try {
1565
- const iterativeDraft = await buildIterativeSds(options.projectKey, context, draft, sdsSections, outputPath, invoke);
1566
- draft = iterativeDraft;
1727
+ if (fastMode) {
1728
+ warnings.push("Fast mode enabled; skipping iterative SDS refinement.");
1567
1729
  }
1568
- catch (error) {
1569
- warnings.push(`Iterative SDS refinement failed; keeping first draft. ${String(error)}`);
1730
+ else {
1731
+ try {
1732
+ const iterativeDraft = await buildIterativeSds(options.projectKey, context, draft, sdsSections, outputPath, invoke);
1733
+ draft = iterativeDraft;
1734
+ }
1735
+ catch (error) {
1736
+ warnings.push(`Iterative SDS refinement failed; keeping first draft. ${String(error)}`);
1737
+ }
1570
1738
  }
1571
1739
  await this.jobService.writeCheckpoint(job.id, {
1572
1740
  stage: "draft_completed",
@@ -1582,10 +1750,15 @@ export class DocsService {
1582
1750
  if (!options.dryRun) {
1583
1751
  await this.writeSdsFile(outputPath, draft);
1584
1752
  if (context.docdexAvailable) {
1585
- const registered = await this.registerSds(outputPath, draft, options.projectKey);
1586
- docdexId = registered.id;
1587
- segments = (registered.segments ?? []).map((s) => s.id);
1588
- await fs.writeFile(`${outputPath}.meta.json`, JSON.stringify({ docdexId, segments, projectKey: options.projectKey }, null, 2), "utf8");
1753
+ try {
1754
+ const registered = await this.registerSds(outputPath, draft, options.projectKey);
1755
+ docdexId = registered.id;
1756
+ segments = (registered.segments ?? []).map((s) => s.id);
1757
+ await fs.writeFile(`${outputPath}.meta.json`, JSON.stringify({ docdexId, segments, projectKey: options.projectKey }, null, 2), "utf8");
1758
+ }
1759
+ catch (error) {
1760
+ warnings.push(`Docdex registration skipped: ${error.message}`);
1761
+ }
1589
1762
  }
1590
1763
  const publicDocsDir = path.join(this.workspace.workspaceRoot, "docs", "sds");
1591
1764
  const shouldMirror = this.workspace.config?.mirrorDocs !== false;
@@ -1613,6 +1786,21 @@ export class DocsService {
1613
1786
  },
1614
1787
  });
1615
1788
  await this.jobService.finishCommandRun(commandRun.id, "succeeded");
1789
+ if (options.rateAgents && agentUsed) {
1790
+ try {
1791
+ const ratingService = await this.ensureRatingService();
1792
+ await ratingService.rate({
1793
+ workspace: this.workspace,
1794
+ agentId: agent.id,
1795
+ commandName: "docs-sds-generate",
1796
+ jobId: job.id,
1797
+ commandRunId: commandRun.id,
1798
+ });
1799
+ }
1800
+ catch (error) {
1801
+ warnings.push(`Agent rating failed: ${error.message ?? String(error)}`);
1802
+ }
1803
+ }
1616
1804
  return {
1617
1805
  jobId: job.id,
1618
1806
  commandRunId: commandRun.id,