@mcoda/core 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +3 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +22 -8
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +53 -34
- package/dist/services/backlog/BacklogService.d.ts.map +1 -1
- package/dist/services/backlog/BacklogService.js +3 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +9 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +251 -35
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +487 -71
- package/dist/services/docs/review/gates/PdrFolderTreeGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrFolderTreeGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrFolderTreeGate.js +151 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.js +109 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.js +128 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.js +153 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.js +109 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.js +128 -0
- package/dist/services/estimate/EstimateService.d.ts +2 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -1
- package/dist/services/estimate/EstimateService.js +54 -0
- package/dist/services/execution/QaTasksService.d.ts +6 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +278 -95
- package/dist/services/execution/TaskSelectionService.d.ts +3 -0
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +33 -0
- package/dist/services/execution/WorkOnTasksService.d.ts +4 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +146 -22
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +43 -4
- package/dist/services/planning/CreateTasksService.d.ts +15 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +592 -81
- package/dist/services/planning/RefineTasksService.d.ts +1 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +88 -2
- package/dist/services/review/CodeReviewService.d.ts +6 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +260 -41
- package/dist/services/shared/ProjectGuidance.d.ts +18 -2
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -1
- package/dist/services/shared/ProjectGuidance.js +535 -34
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RefineTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/RefineTasksService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RefineTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/RefineTasksService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAA6C,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC7G,OAAO,EAIL,kBAAkB,EAClB,iBAAiB,EAKlB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAKrE,UAAU,kBAAmB,SAAQ,kBAAkB;IACrD,SAAS,EAAE,mBAAmB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA6MD,qBAAa,kBAAkB;IAC7B,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;IACvC,OAAO,CAAC,aAAa,CAAC,CAAqB;gBAGzC,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;QAC/B,aAAa,CAAC,EAAE,kBAAkB,CAAC;KACpC;WAYU,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuB1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,cAAc;YAYd,YAAY;YAwBZ,mBAAmB;IA4CjC,OAAO,CAAC,mBAAmB;YAYb,WAAW;IA4KzB,OAAO,CAAC,iBAAiB;YASX,gBAAgB;IAmK9B,OAAO,CAAC,gBAAgB;IA+BxB,OAAO,CAAC,uBAAuB;YA+CjB,aAAa;YA4Eb,gBAAgB;YA+BhB,kBAAkB;IAkChC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,iBAAiB;IA8EzB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,iBAAiB;YAkBX,eAAe;YAqYf,WAAW;IAmGnB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAma3E"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
|
+
import YAML from "yaml";
|
|
3
4
|
import { AgentService } from "@mcoda/agents";
|
|
4
5
|
import { DocdexClient } from "@mcoda/integrations";
|
|
5
6
|
import { READY_TO_CODE_REVIEW } from "@mcoda/shared";
|
|
@@ -21,7 +22,31 @@ const normalizeCreateStatus = (status) => {
|
|
|
21
22
|
const DEFAULT_MAX_TASKS = 250;
|
|
22
23
|
const MAX_AGENT_OUTPUT_CHARS = 10000000;
|
|
23
24
|
const PLANNING_DOC_HINT_PATTERN = /(sds|pdr|rfp|requirements|architecture|openapi|swagger|design)/i;
|
|
25
|
+
const OPENAPI_METHODS = new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
26
|
+
const OPENAPI_HINTS_LIMIT = 20;
|
|
24
27
|
const estimateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
28
|
+
const isPlainObject = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
29
|
+
const parseStructuredDoc = (raw) => {
|
|
30
|
+
if (!raw || raw.trim().length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
try {
|
|
33
|
+
const parsed = YAML.parse(raw);
|
|
34
|
+
if (isPlainObject(parsed))
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// continue
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (isPlainObject(parsed))
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
};
|
|
25
50
|
const extractJson = (raw) => {
|
|
26
51
|
const fencedMatches = [...raw.matchAll(/```json([\s\S]*?)```/g)].map((match) => match[1]);
|
|
27
52
|
const stripped = raw.replace(/<think>[\s\S]*?<\/think>/g, "");
|
|
@@ -224,6 +249,7 @@ export class RefineTasksService {
|
|
|
224
249
|
try {
|
|
225
250
|
await ordering.orderTasks({
|
|
226
251
|
projectKey,
|
|
252
|
+
apply: true,
|
|
227
253
|
});
|
|
228
254
|
}
|
|
229
255
|
finally {
|
|
@@ -606,16 +632,72 @@ export class RefineTasksService {
|
|
|
606
632
|
"Return JSON ONLY matching: { \"operations\": [UpdateTaskOp | SplitTaskOp | MergeTasksOp | UpdateEstimateOp] } where each item has an `op` discriminator (update_task|split_task|merge_tasks|update_estimate).",
|
|
607
633
|
].join("\n\n");
|
|
608
634
|
}
|
|
635
|
+
buildOpenApiHintSummary(docs) {
|
|
636
|
+
const lines = [];
|
|
637
|
+
for (const doc of docs ?? []) {
|
|
638
|
+
const raw = typeof doc?.content === "string" && doc.content.trim().length > 0
|
|
639
|
+
? doc.content
|
|
640
|
+
: Array.isArray(doc?.segments)
|
|
641
|
+
? doc.segments
|
|
642
|
+
.map((segment) => (typeof segment?.content === "string" ? segment.content : ""))
|
|
643
|
+
.filter(Boolean)
|
|
644
|
+
.join("\n\n")
|
|
645
|
+
: "";
|
|
646
|
+
const parsed = parseStructuredDoc(raw);
|
|
647
|
+
if (!parsed)
|
|
648
|
+
continue;
|
|
649
|
+
const paths = parsed.paths;
|
|
650
|
+
if (!isPlainObject(paths))
|
|
651
|
+
continue;
|
|
652
|
+
for (const [apiPath, pathItem] of Object.entries(paths)) {
|
|
653
|
+
if (!isPlainObject(pathItem))
|
|
654
|
+
continue;
|
|
655
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
656
|
+
const normalizedMethod = method.toLowerCase();
|
|
657
|
+
if (!OPENAPI_METHODS.has(normalizedMethod))
|
|
658
|
+
continue;
|
|
659
|
+
if (!isPlainObject(operation))
|
|
660
|
+
continue;
|
|
661
|
+
const hints = operation["x-mcoda-task-hints"];
|
|
662
|
+
if (!isPlainObject(hints))
|
|
663
|
+
continue;
|
|
664
|
+
const service = typeof hints.service === "string" ? hints.service : "-";
|
|
665
|
+
const capability = typeof hints.capability === "string" ? hints.capability : "-";
|
|
666
|
+
const stage = typeof hints.stage === "string" ? hints.stage : "-";
|
|
667
|
+
const complexity = typeof hints.complexity === "number" && Number.isFinite(hints.complexity)
|
|
668
|
+
? hints.complexity.toFixed(1)
|
|
669
|
+
: "-";
|
|
670
|
+
const countItems = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string").length : 0;
|
|
671
|
+
const dependsOn = countItems(hints.depends_on_operations);
|
|
672
|
+
const testRequirements = isPlainObject(hints.test_requirements) ? hints.test_requirements : undefined;
|
|
673
|
+
lines.push(`- ${normalizedMethod.toUpperCase()} ${apiPath} :: service=${service}; capability=${capability}; stage=${stage}; complexity=${complexity}; deps=${dependsOn}; tests(u/c/i/a)=${countItems(testRequirements?.unit)}/${countItems(testRequirements?.component)}/${countItems(testRequirements?.integration)}/${countItems(testRequirements?.api)}`);
|
|
674
|
+
if (lines.length >= OPENAPI_HINTS_LIMIT) {
|
|
675
|
+
return lines.join("\n");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return lines.join("\n");
|
|
681
|
+
}
|
|
609
682
|
async summarizeDocs(projectKey, epicKey, storyKey) {
|
|
610
683
|
const warnings = [];
|
|
611
684
|
const startedAt = Date.now();
|
|
612
685
|
try {
|
|
613
|
-
const query = [epicKey, storyKey, "sds requirements architecture"]
|
|
686
|
+
const query = [epicKey, storyKey, "sds requirements architecture openapi swagger api contracts endpoints"]
|
|
687
|
+
.filter(Boolean)
|
|
688
|
+
.join(" ");
|
|
614
689
|
let docs = await this.docdex.search({
|
|
615
690
|
projectKey,
|
|
616
691
|
profile: "sds",
|
|
617
692
|
query,
|
|
618
693
|
});
|
|
694
|
+
if (!docs || docs.length === 0) {
|
|
695
|
+
docs = await this.docdex.search({
|
|
696
|
+
projectKey,
|
|
697
|
+
profile: "openapi",
|
|
698
|
+
query,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
619
701
|
if (!docs || docs.length === 0) {
|
|
620
702
|
docs = await this.docdex.search({
|
|
621
703
|
projectKey,
|
|
@@ -648,6 +730,10 @@ export class RefineTasksService {
|
|
|
648
730
|
})
|
|
649
731
|
.join("\n");
|
|
650
732
|
const finalSummary = (summary || (selected.length ? selected.map((doc) => `- ${doc.title ?? doc.path ?? doc.id}`).join("\n") : "")).trim();
|
|
733
|
+
const openApiHintSummary = this.buildOpenApiHintSummary(selected);
|
|
734
|
+
const composedSummary = [finalSummary || "(no doc segments found)", openApiHintSummary ? `[OPENAPI_HINTS]\n${openApiHintSummary}` : ""]
|
|
735
|
+
.filter(Boolean)
|
|
736
|
+
.join("\n\n");
|
|
651
737
|
const durationSeconds = (Date.now() - startedAt) / 1000;
|
|
652
738
|
await this.jobService.recordTokenUsage({
|
|
653
739
|
workspaceId: this.workspace.workspaceId,
|
|
@@ -662,7 +748,7 @@ export class RefineTasksService {
|
|
|
662
748
|
timestamp: new Date().toISOString(),
|
|
663
749
|
metadata: { command: "refine-tasks", action: "docdex_search", projectKey, epicKey, storyKey },
|
|
664
750
|
});
|
|
665
|
-
return { summary:
|
|
751
|
+
return { summary: composedSummary, warnings };
|
|
666
752
|
}
|
|
667
753
|
catch (error) {
|
|
668
754
|
warnings.push(`Docdex lookup failed: ${error.message}`);
|
|
@@ -15,6 +15,8 @@ export interface CodeReviewRequest extends TaskSelectionFilters {
|
|
|
15
15
|
agentStream?: boolean;
|
|
16
16
|
rateAgents?: boolean;
|
|
17
17
|
createFollowupTasks?: boolean;
|
|
18
|
+
executionContextPolicy?: "best_effort" | "require_any" | "require_sds_or_openapi";
|
|
19
|
+
emptyDiffApprovalPolicy?: "ready_to_qa" | "complete";
|
|
18
20
|
resumeJobId?: string;
|
|
19
21
|
abortSignal?: AbortSignal;
|
|
20
22
|
}
|
|
@@ -88,6 +90,8 @@ export declare class CodeReviewService {
|
|
|
88
90
|
private resolveAgent;
|
|
89
91
|
private ensureRatingService;
|
|
90
92
|
private resolveTaskComplexity;
|
|
93
|
+
private enforceExecutionContextPolicy;
|
|
94
|
+
private resolveExecutionPlanningContext;
|
|
91
95
|
private selectTasksViaApi;
|
|
92
96
|
private persistState;
|
|
93
97
|
private loadState;
|
|
@@ -102,6 +106,8 @@ export declare class CodeReviewService {
|
|
|
102
106
|
private resolveFindingSlug;
|
|
103
107
|
private applyCommentResolutions;
|
|
104
108
|
private extractPathsFromDiff;
|
|
109
|
+
private readOpenApiFileIfExists;
|
|
110
|
+
private resolveOpenApiContent;
|
|
105
111
|
private buildOpenApiSlice;
|
|
106
112
|
private buildDiff;
|
|
107
113
|
private writeReviewSummaryComment;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeReviewService.d.ts","sourceRoot":"","sources":["../../../src/services/review/CodeReviewService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAMpB,MAAM,WAAW,CAAC;AASnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAsB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"CodeReviewService.d.ts","sourceRoot":"","sources":["../../../src/services/review/CodeReviewService.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import yaml from "yaml";
|
|
|
13
13
|
import { createTaskKeyGenerator } from "../planning/KeyHelpers.js";
|
|
14
14
|
import { RoutingService } from "../agents/RoutingService.js";
|
|
15
15
|
import { AgentRatingService } from "../agents/AgentRatingService.js";
|
|
16
|
-
import { isDocContextExcluded, loadProjectGuidance, normalizeDocType } from "../shared/ProjectGuidance.js";
|
|
16
|
+
import { ensureProjectGuidance, isDocContextExcluded, loadProjectGuidance, normalizeDocType, } from "../shared/ProjectGuidance.js";
|
|
17
17
|
import { buildDocdexUsageGuidance } from "../shared/DocdexGuidance.js";
|
|
18
18
|
import { createTaskCommentSlug, formatTaskCommentBody } from "../tasks/TaskCommentFormatter.js";
|
|
19
19
|
import { AUTH_ERROR_REASON, isAuthErrorMessage } from "../shared/AuthErrors.js";
|
|
@@ -93,6 +93,34 @@ const filterOpenApiContext = (entries, hasOpenApiSnippet) => {
|
|
|
93
93
|
}
|
|
94
94
|
return filtered;
|
|
95
95
|
};
|
|
96
|
+
const normalizeExecutionContextPolicy = (value) => {
|
|
97
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
98
|
+
if (normalized === "require_any" || normalized === "require_sds_or_openapi") {
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
return "best_effort";
|
|
102
|
+
};
|
|
103
|
+
const normalizeEmptyDiffApprovalPolicy = (value, fallback = "complete") => {
|
|
104
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
105
|
+
if (normalized === "complete")
|
|
106
|
+
return "complete";
|
|
107
|
+
if (normalized === "ready_to_qa")
|
|
108
|
+
return "ready_to_qa";
|
|
109
|
+
return fallback;
|
|
110
|
+
};
|
|
111
|
+
const classifyReviewPlanningContextKind = (doc) => {
|
|
112
|
+
const docType = String(doc.docType ?? "").toUpperCase();
|
|
113
|
+
if (docType === "SDS")
|
|
114
|
+
return "sds";
|
|
115
|
+
if (docType === "OPENAPI")
|
|
116
|
+
return "openapi";
|
|
117
|
+
const ref = String(doc.path ?? doc.title ?? "").toLowerCase();
|
|
118
|
+
if (/docs\/sds|\/sds\.md$|(^|\/)sds\.md$/.test(ref))
|
|
119
|
+
return "sds";
|
|
120
|
+
if (/openapi|swagger|(^|\/)openapi\.ya?ml$/.test(ref))
|
|
121
|
+
return "openapi";
|
|
122
|
+
return "doc";
|
|
123
|
+
};
|
|
96
124
|
const estimateTokens = (text) => Math.max(1, Math.ceil((text ?? "").length / 4));
|
|
97
125
|
const summarizeComments = (comments) => {
|
|
98
126
|
if (!comments.length)
|
|
@@ -507,6 +535,63 @@ export class CodeReviewService {
|
|
|
507
535
|
return undefined;
|
|
508
536
|
return Math.min(10, Math.max(1, Math.round(candidate)));
|
|
509
537
|
}
|
|
538
|
+
enforceExecutionContextPolicy(policy, context) {
|
|
539
|
+
if (policy === "best_effort")
|
|
540
|
+
return;
|
|
541
|
+
if (policy === "require_any") {
|
|
542
|
+
if (!context) {
|
|
543
|
+
throw new Error("Review context is required but no planning documents were resolved (policy=require_any).");
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (!context) {
|
|
548
|
+
throw new Error("Review context requires SDS/OpenAPI sources, but none were resolved (policy=require_sds_or_openapi).");
|
|
549
|
+
}
|
|
550
|
+
if (context.kind !== "sds" && context.kind !== "openapi") {
|
|
551
|
+
throw new Error(`Review context policy require_sds_or_openapi rejected source '${context.source}' (kind=${context.kind}).`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async resolveExecutionPlanningContext(projectKey, warnings) {
|
|
555
|
+
if (!projectKey)
|
|
556
|
+
return undefined;
|
|
557
|
+
const pickFirst = async (filter) => {
|
|
558
|
+
const docs = await this.deps.docdex.search({
|
|
559
|
+
profile: "workspace-code",
|
|
560
|
+
...filter,
|
|
561
|
+
});
|
|
562
|
+
return docs[0];
|
|
563
|
+
};
|
|
564
|
+
try {
|
|
565
|
+
const sdsDoc = await pickFirst({ projectKey, docType: "SDS" });
|
|
566
|
+
if (sdsDoc) {
|
|
567
|
+
return {
|
|
568
|
+
source: sdsDoc.id ?? sdsDoc.path ?? sdsDoc.title ?? "sds",
|
|
569
|
+
kind: classifyReviewPlanningContextKind(sdsDoc),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const openapiDoc = await pickFirst({ projectKey, docType: "OPENAPI" });
|
|
573
|
+
if (openapiDoc) {
|
|
574
|
+
return {
|
|
575
|
+
source: openapiDoc.id ?? openapiDoc.path ?? openapiDoc.title ?? "openapi",
|
|
576
|
+
kind: classifyReviewPlanningContextKind(openapiDoc),
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const fallbackDoc = await pickFirst({
|
|
580
|
+
projectKey,
|
|
581
|
+
query: "sds requirements architecture openapi swagger",
|
|
582
|
+
});
|
|
583
|
+
if (!fallbackDoc)
|
|
584
|
+
return undefined;
|
|
585
|
+
return {
|
|
586
|
+
source: fallbackDoc.id ?? fallbackDoc.path ?? fallbackDoc.title ?? "workspace-code",
|
|
587
|
+
kind: classifyReviewPlanningContextKind(fallbackDoc),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
warnings.push(`Review context preflight failed to query docdex: ${error.message}`);
|
|
592
|
+
return undefined;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
510
595
|
async selectTasksViaApi(filters) {
|
|
511
596
|
// Prefer the backlog/task OpenAPI surface (via BacklogService) to mirror API filtering semantics.
|
|
512
597
|
const backlog = await BacklogService.create(this.workspace);
|
|
@@ -570,7 +655,7 @@ export class CodeReviewService {
|
|
|
570
655
|
}
|
|
571
656
|
return Array.from(hints).slice(0, 8);
|
|
572
657
|
}
|
|
573
|
-
async gatherDocContext(taskTitle, paths, acceptance, docLinks = []) {
|
|
658
|
+
async gatherDocContext(taskTitle, paths, acceptance, docLinks = [], projectKey) {
|
|
574
659
|
const snippets = [];
|
|
575
660
|
const warnings = [];
|
|
576
661
|
if (typeof this.deps.docdex?.ensureRepoScope === "function") {
|
|
@@ -597,44 +682,74 @@ export class CodeReviewService {
|
|
|
597
682
|
}
|
|
598
683
|
return normalized.docType;
|
|
599
684
|
};
|
|
600
|
-
|
|
685
|
+
const runSearch = async (filter, label) => {
|
|
601
686
|
try {
|
|
602
|
-
|
|
603
|
-
query,
|
|
604
|
-
profile: "workspace-code",
|
|
605
|
-
}), DOCDEX_TIMEOUT_MS, `docdex search for "${query}"`);
|
|
606
|
-
const filteredDocs = docs.filter((doc) => !isDocContextExcluded(doc.path ?? doc.title ?? doc.id, false));
|
|
607
|
-
snippets.push(...filteredDocs.slice(0, 2).map((doc) => {
|
|
608
|
-
const content = (doc.segments?.[0]?.content ?? doc.content ?? "").slice(0, 400);
|
|
609
|
-
const ref = doc.path ?? doc.id ?? doc.title ?? query;
|
|
610
|
-
return `- [${resolveDocType(doc)}] ${ref}: ${content}`;
|
|
611
|
-
}));
|
|
687
|
+
return await withTimeout(this.deps.docdex.search(filter), DOCDEX_TIMEOUT_MS, label);
|
|
612
688
|
}
|
|
613
689
|
catch (error) {
|
|
614
690
|
if (!reindexed && typeof this.deps.docdex.reindex === "function") {
|
|
615
691
|
reindexed = true;
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const docs = await withTimeout(this.deps.docdex.search({
|
|
619
|
-
query,
|
|
620
|
-
profile: "workspace-code",
|
|
621
|
-
}), DOCDEX_TIMEOUT_MS, `docdex search for "${query}" after reindex`);
|
|
622
|
-
const filteredDocs = docs.filter((doc) => !isDocContextExcluded(doc.path ?? doc.title ?? doc.id, false));
|
|
623
|
-
snippets.push(...filteredDocs.slice(0, 2).map((doc) => {
|
|
624
|
-
const content = (doc.segments?.[0]?.content ?? doc.content ?? "").slice(0, 400);
|
|
625
|
-
const ref = doc.path ?? doc.id ?? doc.title ?? query;
|
|
626
|
-
return `- [${resolveDocType(doc)}] ${ref}: ${content}`;
|
|
627
|
-
}));
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
catch (retryError) {
|
|
631
|
-
warnings.push(`docdex search failed after reindex for ${query}: ${retryError.message}`);
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
692
|
+
await this.deps.docdex.reindex();
|
|
693
|
+
return await withTimeout(this.deps.docdex.search(filter), DOCDEX_TIMEOUT_MS, `${label} after reindex`);
|
|
634
694
|
}
|
|
695
|
+
throw error;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
const pushDocs = (docs, query) => {
|
|
699
|
+
const filteredDocs = docs.filter((doc) => !isDocContextExcluded(doc.path ?? doc.title ?? doc.id, false));
|
|
700
|
+
snippets.push(...filteredDocs.slice(0, 2).map((doc) => {
|
|
701
|
+
const content = (doc.segments?.[0]?.content ?? doc.content ?? "").slice(0, 400);
|
|
702
|
+
const ref = doc.path ?? doc.id ?? doc.title ?? query;
|
|
703
|
+
return `- [${resolveDocType(doc)}] ${ref}: ${content}`;
|
|
704
|
+
}));
|
|
705
|
+
return filteredDocs.length;
|
|
706
|
+
};
|
|
707
|
+
for (const query of queries) {
|
|
708
|
+
let structuredHits = 0;
|
|
709
|
+
try {
|
|
710
|
+
structuredHits += pushDocs(await runSearch({
|
|
711
|
+
query,
|
|
712
|
+
projectKey,
|
|
713
|
+
docType: "SDS",
|
|
714
|
+
profile: "workspace-code",
|
|
715
|
+
}, `docdex search for "${query}"`), query);
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
warnings.push(`docdex search failed for SDS query ${query}: ${error.message}`);
|
|
719
|
+
}
|
|
720
|
+
try {
|
|
721
|
+
structuredHits += pushDocs(await runSearch({
|
|
722
|
+
query,
|
|
723
|
+
projectKey,
|
|
724
|
+
docType: "OPENAPI",
|
|
725
|
+
profile: "workspace-code",
|
|
726
|
+
}, `docdex search for "${query}"`), query);
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
warnings.push(`docdex search failed for OPENAPI query ${query}: ${error.message}`);
|
|
730
|
+
}
|
|
731
|
+
if (structuredHits > 0) {
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
pushDocs(await runSearch({
|
|
736
|
+
query,
|
|
737
|
+
projectKey,
|
|
738
|
+
profile: "workspace-code",
|
|
739
|
+
}, `docdex search for "${query}"`), query);
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
635
742
|
warnings.push(`docdex search failed for ${query}: ${error.message}`);
|
|
636
743
|
}
|
|
637
744
|
}
|
|
745
|
+
if (!snippets.length && projectKey) {
|
|
746
|
+
try {
|
|
747
|
+
pushDocs(await runSearch({ projectKey, query: "sds openapi requirements architecture", profile: "workspace-code" }, "docdex fallback search"), "fallback");
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
warnings.push(`docdex fallback search failed: ${error.message}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
638
753
|
const normalizeDocLink = (value) => {
|
|
639
754
|
const trimmed = value.trim();
|
|
640
755
|
const stripped = trimmed.replace(/^docdex:/i, "").replace(/^doc:/i, "");
|
|
@@ -728,7 +843,7 @@ export class CodeReviewService {
|
|
|
728
843
|
}
|
|
729
844
|
async buildHistorySummary(taskId) {
|
|
730
845
|
const comments = await this.deps.workspaceRepo.listTaskComments(taskId, {
|
|
731
|
-
sourceCommands: ["work-on-tasks", "code-review"],
|
|
846
|
+
sourceCommands: ["work-on-tasks", "code-review", "qa-tasks"],
|
|
732
847
|
limit: 10,
|
|
733
848
|
});
|
|
734
849
|
const lastReview = await this.deps.workspaceRepo.getLatestTaskReview(taskId);
|
|
@@ -755,7 +870,7 @@ export class CodeReviewService {
|
|
|
755
870
|
}
|
|
756
871
|
async loadCommentContext(taskId) {
|
|
757
872
|
const comments = await this.deps.workspaceRepo.listTaskComments(taskId, {
|
|
758
|
-
sourceCommands: ["code-review"],
|
|
873
|
+
sourceCommands: ["code-review", "qa-tasks"],
|
|
759
874
|
limit: 50,
|
|
760
875
|
});
|
|
761
876
|
const unresolved = comments.filter((comment) => !comment.resolvedAt);
|
|
@@ -949,10 +1064,84 @@ export class CodeReviewService {
|
|
|
949
1064
|
}
|
|
950
1065
|
return Array.from(paths);
|
|
951
1066
|
}
|
|
952
|
-
async
|
|
953
|
-
const
|
|
1067
|
+
async readOpenApiFileIfExists(targetPath) {
|
|
1068
|
+
const absolutePath = path.isAbsolute(targetPath) ? targetPath : path.join(this.workspace.workspaceRoot, targetPath);
|
|
954
1069
|
try {
|
|
955
|
-
const content = await fs.readFile(
|
|
1070
|
+
const content = await fs.readFile(absolutePath, "utf8");
|
|
1071
|
+
return { sourcePath: absolutePath, content };
|
|
1072
|
+
}
|
|
1073
|
+
catch {
|
|
1074
|
+
return undefined;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
async resolveOpenApiContent(task, projectKey) {
|
|
1078
|
+
const metadata = task.metadata ?? {};
|
|
1079
|
+
const metadataPath = (typeof metadata.openapi_path === "string" && metadata.openapi_path.trim()) ||
|
|
1080
|
+
(typeof metadata.openapiPath === "string" && metadata.openapiPath.trim()) ||
|
|
1081
|
+
undefined;
|
|
1082
|
+
const configPath = (typeof this.workspace.config?.openapiPath === "string" && this.workspace.config.openapiPath.trim()) ||
|
|
1083
|
+
(typeof this.workspace.config?.openapi === "string" && this.workspace.config.openapi.trim()) ||
|
|
1084
|
+
undefined;
|
|
1085
|
+
const docLinkPaths = Array.isArray(metadata.doc_links)
|
|
1086
|
+
? metadata.doc_links
|
|
1087
|
+
.filter((entry) => typeof entry === "string")
|
|
1088
|
+
.map((entry) => entry.replace(/^docdex:/i, "").replace(/^doc:/i, "").trim())
|
|
1089
|
+
.filter((entry) => /\.(ya?ml|json)$/i.test(entry) && /openapi|swagger/i.test(entry))
|
|
1090
|
+
: [];
|
|
1091
|
+
const candidates = [
|
|
1092
|
+
metadataPath,
|
|
1093
|
+
configPath,
|
|
1094
|
+
...docLinkPaths,
|
|
1095
|
+
"openapi/mcoda.yaml",
|
|
1096
|
+
"openapi/openapi.yaml",
|
|
1097
|
+
"openapi/openapi.yml",
|
|
1098
|
+
"openapi.yaml",
|
|
1099
|
+
"openapi.yml",
|
|
1100
|
+
"docs/openapi.yaml",
|
|
1101
|
+
"docs/openapi.yml",
|
|
1102
|
+
"openapi/swagger.yaml",
|
|
1103
|
+
"openapi/swagger.yml",
|
|
1104
|
+
].filter((entry) => Boolean(entry && entry.trim()));
|
|
1105
|
+
const seen = new Set();
|
|
1106
|
+
for (const candidate of candidates) {
|
|
1107
|
+
if (seen.has(candidate))
|
|
1108
|
+
continue;
|
|
1109
|
+
seen.add(candidate);
|
|
1110
|
+
const loaded = await this.readOpenApiFileIfExists(candidate);
|
|
1111
|
+
if (loaded) {
|
|
1112
|
+
return { source: loaded.sourcePath, content: loaded.content };
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
const docs = await this.deps.docdex.search({
|
|
1117
|
+
projectKey,
|
|
1118
|
+
docType: "OPENAPI",
|
|
1119
|
+
profile: "workspace-code",
|
|
1120
|
+
});
|
|
1121
|
+
for (const doc of docs) {
|
|
1122
|
+
if (doc.path) {
|
|
1123
|
+
const loaded = await this.readOpenApiFileIfExists(doc.path);
|
|
1124
|
+
if (loaded) {
|
|
1125
|
+
return { source: loaded.sourcePath, content: loaded.content };
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
const content = (doc.segments?.map((segment) => segment.content ?? "").join("\n") || doc.content || "").trim();
|
|
1129
|
+
if (content.length > 0) {
|
|
1130
|
+
return { source: doc.path ?? doc.id ?? doc.title ?? "docdex:openapi", content };
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
// Best-effort fallback; no OpenAPI is acceptable for non-strict policies.
|
|
1136
|
+
}
|
|
1137
|
+
return undefined;
|
|
1138
|
+
}
|
|
1139
|
+
async buildOpenApiSlice(changedPaths, acceptance, task, projectKey) {
|
|
1140
|
+
const resolved = await this.resolveOpenApiContent(task, projectKey);
|
|
1141
|
+
if (!resolved)
|
|
1142
|
+
return undefined;
|
|
1143
|
+
try {
|
|
1144
|
+
const content = resolved.content;
|
|
956
1145
|
const parsed = yaml.parse(content);
|
|
957
1146
|
const pathHints = this.componentHintsFromPaths(changedPaths);
|
|
958
1147
|
const criteriaHints = (acceptance ?? []).map((c) => c.toLowerCase()).slice(0, 5);
|
|
@@ -990,7 +1179,7 @@ export class CodeReviewService {
|
|
|
990
1179
|
return rendered.slice(0, 8000);
|
|
991
1180
|
}
|
|
992
1181
|
catch {
|
|
993
|
-
return
|
|
1182
|
+
return resolved.content.slice(0, 4000);
|
|
994
1183
|
}
|
|
995
1184
|
}
|
|
996
1185
|
async buildDiff(task, baseRef, fileScope) {
|
|
@@ -1258,6 +1447,8 @@ export class CodeReviewService {
|
|
|
1258
1447
|
await this.ensureMcoda();
|
|
1259
1448
|
const agentStream = request.agentStream !== false;
|
|
1260
1449
|
const baseRef = request.baseRef ?? this.workspace.config?.branch ?? DEFAULT_BASE_BRANCH;
|
|
1450
|
+
let executionContextPolicy = normalizeExecutionContextPolicy(request.executionContextPolicy);
|
|
1451
|
+
let emptyDiffApprovalPolicy = normalizeEmptyDiffApprovalPolicy(request.emptyDiffApprovalPolicy, "complete");
|
|
1261
1452
|
const ignoreStatusFilter = Boolean(request.taskKeys?.length) || request.ignoreStatusFilter === true;
|
|
1262
1453
|
const rawStatusFilter = ignoreStatusFilter
|
|
1263
1454
|
? []
|
|
@@ -1278,6 +1469,14 @@ export class CodeReviewService {
|
|
|
1278
1469
|
if (rejected.length > 0 && !ignoreStatusFilter) {
|
|
1279
1470
|
warnings.push(`code-review ignores unsupported statuses: ${rejected.join(", ")}. Allowed: ${REVIEW_ALLOWED_STATUSES.join(", ")}.`);
|
|
1280
1471
|
}
|
|
1472
|
+
const executionContext = await this.resolveExecutionPlanningContext(request.projectKey, warnings);
|
|
1473
|
+
try {
|
|
1474
|
+
this.enforceExecutionContextPolicy(executionContextPolicy, executionContext);
|
|
1475
|
+
}
|
|
1476
|
+
catch (error) {
|
|
1477
|
+
await this.deps.jobService.finishCommandRun(commandRun.id, "failed", error instanceof Error ? error.message : String(error));
|
|
1478
|
+
throw error;
|
|
1479
|
+
}
|
|
1281
1480
|
let allowFollowups = request.createFollowupTasks === true;
|
|
1282
1481
|
let selectedTasks = [];
|
|
1283
1482
|
if (request.resumeJobId) {
|
|
@@ -1358,6 +1557,9 @@ export class CodeReviewService {
|
|
|
1358
1557
|
agent: request.agentName,
|
|
1359
1558
|
agentStream,
|
|
1360
1559
|
createFollowupTasks: allowFollowups,
|
|
1560
|
+
executionContextPolicy,
|
|
1561
|
+
executionContext: executionContext ? { source: executionContext.source, kind: executionContext.kind } : undefined,
|
|
1562
|
+
emptyDiffApprovalPolicy,
|
|
1361
1563
|
},
|
|
1362
1564
|
totalItems: selectedTaskIds.length,
|
|
1363
1565
|
processedItems: 0,
|
|
@@ -1425,9 +1627,26 @@ export class CodeReviewService {
|
|
|
1425
1627
|
}
|
|
1426
1628
|
const prompts = await this.loadPrompts(agent.id);
|
|
1427
1629
|
const extras = await this.loadRunbookAndChecklists();
|
|
1428
|
-
|
|
1630
|
+
try {
|
|
1631
|
+
const ensuredGuidance = await ensureProjectGuidance(this.workspace.workspaceRoot, {
|
|
1632
|
+
mcodaDir: this.workspace.mcodaDir,
|
|
1633
|
+
projectKey: request.projectKey,
|
|
1634
|
+
});
|
|
1635
|
+
for (const warning of ensuredGuidance.warnings ?? []) {
|
|
1636
|
+
warnings.push(`project_guidance_warning:${warning}`);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
catch (error) {
|
|
1640
|
+
warnings.push(`project_guidance_bootstrap_failed:${error instanceof Error ? error.message : String(error)}`);
|
|
1641
|
+
}
|
|
1642
|
+
const projectGuidance = await loadProjectGuidance(this.workspace.workspaceRoot, this.workspace.mcodaDir, {
|
|
1643
|
+
projectKey: request.projectKey,
|
|
1644
|
+
});
|
|
1429
1645
|
if (projectGuidance) {
|
|
1430
1646
|
console.info(`[code-review] loaded project guidance from ${projectGuidance.source}`);
|
|
1647
|
+
for (const warning of projectGuidance.warnings ?? []) {
|
|
1648
|
+
warnings.push(`project_guidance_warning:${warning}`);
|
|
1649
|
+
}
|
|
1431
1650
|
}
|
|
1432
1651
|
const guidanceBlock = projectGuidance?.content ? `Project Guidance (read first):\n${projectGuidance.content}` : undefined;
|
|
1433
1652
|
const systemPrompts = [guidanceBlock, prompts.jobPrompt, prompts.characterPrompt, prompts.commandPrompt, ...extras].filter(Boolean);
|
|
@@ -1685,7 +1904,7 @@ export class CodeReviewService {
|
|
|
1685
1904
|
});
|
|
1686
1905
|
const changedPaths = this.extractPathsFromDiff(diff);
|
|
1687
1906
|
const diffMeta = { diffEmpty, changedPaths };
|
|
1688
|
-
const docLinks = await this.gatherDocContext(task.title, changedPaths.length ? changedPaths : allowedFiles, task.acceptanceCriteria, Array.isArray(task.metadata?.doc_links) ? task.metadata.doc_links : []);
|
|
1907
|
+
const docLinks = await this.gatherDocContext(task.title, changedPaths.length ? changedPaths : allowedFiles, task.acceptanceCriteria, Array.isArray(task.metadata?.doc_links) ? task.metadata.doc_links : [], request.projectKey);
|
|
1689
1908
|
if (docLinks.warnings.length)
|
|
1690
1909
|
warnings.push(...docLinks.warnings);
|
|
1691
1910
|
await this.deps.workspaceRepo.insertTaskLog({
|
|
@@ -1696,7 +1915,7 @@ export class CodeReviewService {
|
|
|
1696
1915
|
message: "Docdex context gathered",
|
|
1697
1916
|
details: { snippets: docLinks.snippets },
|
|
1698
1917
|
});
|
|
1699
|
-
const openapiSnippet = await this.buildOpenApiSlice(changedPaths, task.acceptanceCriteria);
|
|
1918
|
+
const openapiSnippet = await this.buildOpenApiSlice(changedPaths, task.acceptanceCriteria, task, request.projectKey);
|
|
1700
1919
|
if (!openapiSnippet) {
|
|
1701
1920
|
warnings.push("OpenAPI spec not found; proceeding without snippet");
|
|
1702
1921
|
}
|
|
@@ -2140,7 +2359,7 @@ export class CodeReviewService {
|
|
|
2140
2359
|
if (!request.dryRun) {
|
|
2141
2360
|
const approveDecision = parsed.decision === "approve" || parsed.decision === "info_only";
|
|
2142
2361
|
if (approveDecision) {
|
|
2143
|
-
if (diffEmpty) {
|
|
2362
|
+
if (diffEmpty && emptyDiffApprovalPolicy === "complete") {
|
|
2144
2363
|
await this.stateService.markCompleted(task, { review_no_changes: true }, statusContext);
|
|
2145
2364
|
taskStatusUpdate = "completed";
|
|
2146
2365
|
}
|