@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.
- package/CHANGELOG.md +3 -0
- package/dist/api/AgentsApi.d.ts +8 -1
- package/dist/api/AgentsApi.d.ts.map +1 -1
- package/dist/api/AgentsApi.js +70 -0
- package/dist/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +2 -0
- package/dist/api/TasksApi.d.ts.map +1 -1
- package/dist/api/TasksApi.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +3 -1
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +2 -0
- package/dist/services/agents/AgentRatingFormula.d.ts +27 -0
- package/dist/services/agents/AgentRatingFormula.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingFormula.js +45 -0
- package/dist/services/agents/AgentRatingService.d.ts +41 -0
- package/dist/services/agents/AgentRatingService.d.ts.map +1 -0
- package/dist/services/agents/AgentRatingService.js +299 -0
- package/dist/services/agents/GatewayAgentService.d.ts +3 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -1
- package/dist/services/agents/GatewayAgentService.js +68 -24
- package/dist/services/agents/GatewayHandoff.d.ts +7 -0
- package/dist/services/agents/GatewayHandoff.d.ts.map +1 -0
- package/dist/services/agents/GatewayHandoff.js +108 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +1 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +19 -16
- package/dist/services/docs/DocsService.d.ts +11 -1
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +240 -52
- package/dist/services/execution/GatewayTrioService.d.ts +133 -0
- package/dist/services/execution/GatewayTrioService.d.ts.map +1 -0
- package/dist/services/execution/GatewayTrioService.js +1125 -0
- package/dist/services/execution/QaFollowupService.d.ts +1 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -1
- package/dist/services/execution/QaFollowupService.js +1 -0
- package/dist/services/execution/QaProfileService.d.ts +6 -0
- package/dist/services/execution/QaProfileService.d.ts.map +1 -1
- package/dist/services/execution/QaProfileService.js +165 -3
- package/dist/services/execution/QaTasksService.d.ts +18 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +712 -34
- package/dist/services/execution/WorkOnTasksService.d.ts +14 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +1497 -240
- package/dist/services/openapi/OpenApiService.d.ts +10 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +66 -10
- package/dist/services/planning/CreateTasksService.d.ts +6 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +261 -28
- package/dist/services/planning/RefineTasksService.d.ts +5 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +184 -35
- package/dist/services/review/CodeReviewService.d.ts +14 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +657 -61
- package/dist/services/shared/ProjectGuidance.d.ts +6 -0
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -0
- package/dist/services/shared/ProjectGuidance.js +21 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts +20 -0
- package/dist/services/tasks/TaskCommentFormatter.d.ts.map +1 -0
- package/dist/services/tasks/TaskCommentFormatter.js +54 -0
- package/dist/workspace/WorkspaceManager.d.ts +4 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -1
- package/dist/workspace/WorkspaceManager.js +3 -0
- 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
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
await this.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"DocsService.d.ts","sourceRoot":"","sources":["../../../src/services/docs/DocsService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AAYnE,OAAO,EAAE,UAAU,EAAiB,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA4qCD,qBAAa,WAAW;IAUpB,OAAO,CAAC,SAAS;IATnB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAC,CAAqB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAsB;gBAGlC,SAAS,EAAE,mBAAmB,EACtC,IAAI,EAAE;QACJ,MAAM,CAAC,EAAE,YAAY,CAAC;QACtB,UAAU,CAAC,EAAE,UAAU,CAAC;QACxB,YAAY,EAAE,YAAY,CAAC;QAC3B,IAAI,EAAE,gBAAgB,CAAC;QACvB,cAAc,EAAE,cAAc,CAAC;QAC/B,aAAa,CAAC,EAAE,mBAAmB,CAAC;QACpC,aAAa,CAAC,EAAE,kBAAkB,CAAC;QACnC,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB;WAWU,MAAM,CAAC,SAAS,EAAE,mBAAmB,EAAE,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAY5G,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,oBAAoB;YAKd,eAAe;YAsBf,YAAY;YAaZ,mBAAmB;YAiBnB,YAAY;YAKZ,WAAW;YAeX,YAAY;YAKZ,sBAAsB;YAatB,WAAW;YAeX,WAAW;IA+BnB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAiP5D,YAAY;IA4EpB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAsU3E"}
|
|
@@ -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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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 (
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
draft = iterativeDraft;
|
|
1370
|
+
if (fastMode) {
|
|
1371
|
+
context.warnings.push("Fast mode enabled; skipping iterative PDR refinement.");
|
|
1247
1372
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
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
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
-
|
|
1543
|
-
|
|
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
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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
|
-
|
|
1565
|
-
|
|
1566
|
-
draft = iterativeDraft;
|
|
1727
|
+
if (fastMode) {
|
|
1728
|
+
warnings.push("Fast mode enabled; skipping iterative SDS refinement.");
|
|
1567
1729
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
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
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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,
|