@mcoda/core 0.1.27 → 0.1.28
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/AgentsApi.d.ts +9 -1
- package/dist/api/AgentsApi.d.ts.map +1 -1
- package/dist/api/AgentsApi.js +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/services/execution/QaTasksService.d.ts +2 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +181 -20
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +183 -7
- package/dist/services/planning/CreateTasksService.d.ts +8 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +350 -39
- package/dist/services/planning/SdsPreflightService.d.ts +98 -0
- package/dist/services/planning/SdsPreflightService.d.ts.map +1 -0
- package/dist/services/planning/SdsPreflightService.js +1093 -0
- package/dist/services/planning/TaskSufficiencyService.d.ts.map +1 -1
- package/dist/services/planning/TaskSufficiencyService.js +178 -83
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +149 -7
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSufficiencyService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/TaskSufficiencyService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAiC,MAAM,WAAW,CAAC;AAE/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"TaskSufficiencyService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/TaskSufficiencyService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAiC,MAAM,WAAW,CAAC;AAE/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAmGnD,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,aAAa,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,UAAU,EAAE,6BAA6B,EAAE,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,KAAK,mBAAmB,GAAG;IACzB,aAAa,EAAE,mBAAmB,CAAC;IACnC,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AAsNF,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;gBAG9C,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,SAAS,GAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO;WAS9D,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAU9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,gBAAgB;YA4BhB,iBAAiB;YAmCjB,cAAc;YAcd,mBAAmB;IAwEjC,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,aAAa;IA+BrB,OAAO,CAAC,cAAc;YA2BR,iBAAiB;YAyGjB,cAAc;YAkId,oBAAoB;IAkB5B,QAAQ,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC;CA2U1F"}
|
|
@@ -6,16 +6,17 @@ import { JobService } from "../jobs/JobService.js";
|
|
|
6
6
|
import { createEpicKeyGenerator, createStoryKeyGenerator, createTaskKeyGenerator } from "./KeyHelpers.js";
|
|
7
7
|
const DEFAULT_MAX_ITERATIONS = 5;
|
|
8
8
|
const DEFAULT_MAX_TASKS_PER_ITERATION = 24;
|
|
9
|
-
const DEFAULT_MIN_COVERAGE_RATIO =
|
|
9
|
+
const DEFAULT_MIN_COVERAGE_RATIO = 1;
|
|
10
10
|
const SDS_SCAN_MAX_FILES = 120;
|
|
11
11
|
const SDS_HEADING_LIMIT = 200;
|
|
12
12
|
const SDS_FOLDER_LIMIT = 240;
|
|
13
|
-
const
|
|
13
|
+
const GAP_BUNDLE_MAX_ANCHORS = 3;
|
|
14
|
+
const COVERAGE_HEURISTICS_VERSION = 2;
|
|
14
15
|
const REPORT_FILE_NAME = "task-sufficiency-report.json";
|
|
15
16
|
const ignoredDirs = new Set([".git", "node_modules", "dist", "build", ".mcoda", ".docdex"]);
|
|
16
17
|
const sdsFilenamePattern = /(sds|software[-_ ]design|system[-_ ]design|design[-_ ]spec)/i;
|
|
17
18
|
const sdsContentPattern = /(software design specification|system design specification|^#\s*sds\b)/im;
|
|
18
|
-
const nonImplementationHeadingPattern = /\b(revision history|table of contents|purpose|scope|definitions?|abbreviations?|glossary|references?|appendix|document control|authors?)\b/i;
|
|
19
|
+
const nonImplementationHeadingPattern = /\b(software design specification|system design specification|\bsds\b|revision history|table of contents|purpose|scope|definitions?|abbreviations?|glossary|references?|appendix|document control|authors?)\b/i;
|
|
19
20
|
const likelyImplementationHeadingPattern = /\b(architecture|entity|entities|service|services|module|modules|component|components|pipeline|workflow|api|endpoint|schema|model|feature|store|database|ingestion|training|inference|ui|frontend|backend|ops|observability|security|deployment|solver|integration|testing|validation|contract|index|mapping|registry|cache|queue|event|job|task|migration|controller|router|policy)\b/i;
|
|
20
21
|
const repoRootSegments = new Set([
|
|
21
22
|
"apps",
|
|
@@ -43,6 +44,27 @@ const repoRootSegments = new Set([
|
|
|
43
44
|
"web",
|
|
44
45
|
]);
|
|
45
46
|
const headingNoiseTokens = new Set(["and", "for", "from", "into", "the", "with"]);
|
|
47
|
+
const coverageStopTokens = new Set([
|
|
48
|
+
"about",
|
|
49
|
+
"across",
|
|
50
|
+
"after",
|
|
51
|
+
"before",
|
|
52
|
+
"between",
|
|
53
|
+
"from",
|
|
54
|
+
"into",
|
|
55
|
+
"over",
|
|
56
|
+
"under",
|
|
57
|
+
"using",
|
|
58
|
+
"with",
|
|
59
|
+
"without",
|
|
60
|
+
"into",
|
|
61
|
+
"onto",
|
|
62
|
+
"the",
|
|
63
|
+
"and",
|
|
64
|
+
"for",
|
|
65
|
+
"of",
|
|
66
|
+
"to",
|
|
67
|
+
]);
|
|
46
68
|
const normalizeText = (value) => value
|
|
47
69
|
.toLowerCase()
|
|
48
70
|
.replace(/[`*_]/g, " ")
|
|
@@ -123,13 +145,32 @@ const deriveFolderDomain = (entry) => {
|
|
|
123
145
|
const extractMarkdownHeadings = (content, limit) => {
|
|
124
146
|
if (!content)
|
|
125
147
|
return [];
|
|
126
|
-
const
|
|
148
|
+
const lines = content.split(/\r?\n/);
|
|
127
149
|
const headings = [];
|
|
128
|
-
for (
|
|
129
|
-
const
|
|
130
|
-
if (!
|
|
150
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
151
|
+
const line = lines[index]?.trim() ?? "";
|
|
152
|
+
if (!line)
|
|
131
153
|
continue;
|
|
132
|
-
|
|
154
|
+
const hashHeading = line.match(/^#{1,6}\s+(.+)$/);
|
|
155
|
+
if (hashHeading) {
|
|
156
|
+
const heading = hashHeading[1]?.replace(/#+$/, "").trim();
|
|
157
|
+
if (heading)
|
|
158
|
+
headings.push(heading);
|
|
159
|
+
}
|
|
160
|
+
else if (index + 1 < lines.length &&
|
|
161
|
+
/^[=-]{3,}\s*$/.test((lines[index + 1] ?? "").trim()) &&
|
|
162
|
+
!line.startsWith("-") &&
|
|
163
|
+
!line.startsWith("*")) {
|
|
164
|
+
headings.push(line);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const numberedHeading = line.match(/^(\d+(?:\.\d+)+)\s+(.+)$/);
|
|
168
|
+
if (numberedHeading) {
|
|
169
|
+
const heading = `${numberedHeading[1]} ${numberedHeading[2]}`.trim();
|
|
170
|
+
if (/[a-z]/i.test(heading))
|
|
171
|
+
headings.push(heading);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
133
174
|
if (headings.length >= limit)
|
|
134
175
|
break;
|
|
135
176
|
}
|
|
@@ -158,39 +199,73 @@ const extractFolderEntries = (content, limit) => {
|
|
|
158
199
|
}
|
|
159
200
|
return unique(candidates).slice(0, limit);
|
|
160
201
|
};
|
|
202
|
+
const tokenizeCoverageSignal = (value) => unique(value
|
|
203
|
+
.split(/\s+/)
|
|
204
|
+
.map((token) => token.replace(/[^a-z0-9._-]+/g, ""))
|
|
205
|
+
.filter((token) => token.length >= 3 && !coverageStopTokens.has(token)));
|
|
206
|
+
const buildBigrams = (tokens) => {
|
|
207
|
+
const bigrams = [];
|
|
208
|
+
for (let index = 0; index < tokens.length - 1; index += 1) {
|
|
209
|
+
const left = tokens[index];
|
|
210
|
+
const right = tokens[index + 1];
|
|
211
|
+
if (!left || !right)
|
|
212
|
+
continue;
|
|
213
|
+
bigrams.push(`${left} ${right}`);
|
|
214
|
+
}
|
|
215
|
+
return unique(bigrams);
|
|
216
|
+
};
|
|
161
217
|
const headingCovered = (corpus, heading) => {
|
|
162
|
-
const normalized = normalizeText(heading);
|
|
218
|
+
const normalized = normalizeText(normalizeHeadingCandidate(heading));
|
|
163
219
|
if (!normalized)
|
|
164
220
|
return true;
|
|
165
221
|
if (corpus.includes(normalized))
|
|
166
222
|
return true;
|
|
167
|
-
const tokens = normalized
|
|
168
|
-
.split(/\s+/)
|
|
169
|
-
.filter((token) => token.length >= 4)
|
|
170
|
-
.slice(0, 8);
|
|
223
|
+
const tokens = tokenizeCoverageSignal(normalized).slice(0, 10);
|
|
171
224
|
if (tokens.length === 0)
|
|
172
225
|
return true;
|
|
173
226
|
const hitCount = tokens.filter((token) => corpus.includes(token)).length;
|
|
174
|
-
const
|
|
175
|
-
|
|
227
|
+
const requiredHits = tokens.length <= 2
|
|
228
|
+
? tokens.length
|
|
229
|
+
: tokens.length <= 4
|
|
230
|
+
? 2
|
|
231
|
+
: Math.min(4, Math.ceil(tokens.length * 0.6));
|
|
232
|
+
if (hitCount < requiredHits)
|
|
233
|
+
return false;
|
|
234
|
+
if (tokens.length >= 3) {
|
|
235
|
+
const longestToken = tokens.reduce((longest, token) => (token.length > longest.length ? token : longest), "");
|
|
236
|
+
if (longestToken.length >= 6 && !corpus.includes(longestToken))
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
const bigrams = buildBigrams(tokens);
|
|
240
|
+
if (tokens.length >= 3 && bigrams.length > 0 && !bigrams.some((bigram) => corpus.includes(bigram))) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
176
244
|
};
|
|
177
245
|
const folderEntryCovered = (corpus, entry) => {
|
|
178
|
-
const
|
|
179
|
-
if (!
|
|
246
|
+
const normalizedEntry = normalizeFolderEntry(entry)?.toLowerCase().replace(/\/+/g, "/");
|
|
247
|
+
if (!normalizedEntry)
|
|
180
248
|
return true;
|
|
181
|
-
|
|
249
|
+
const corpusTight = corpus.replace(/\s+/g, "");
|
|
250
|
+
if (corpusTight.includes(normalizedEntry.replace(/\s+/g, "")))
|
|
182
251
|
return true;
|
|
183
|
-
const segments =
|
|
252
|
+
const segments = normalizedEntry
|
|
253
|
+
.split("/")
|
|
254
|
+
.map((segment) => segment.trim().replace(/[^a-z0-9._-]+/g, ""))
|
|
255
|
+
.filter(Boolean);
|
|
184
256
|
if (segments.length === 0)
|
|
185
257
|
return true;
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
258
|
+
const tailSegments = unique(segments.slice(Math.max(0, segments.length - 3)));
|
|
259
|
+
const hitCount = tailSegments.filter((segment) => corpus.includes(segment)).length;
|
|
260
|
+
const requiredHits = tailSegments.length <= 1 ? 1 : Math.min(2, tailSegments.length);
|
|
261
|
+
if (hitCount < requiredHits)
|
|
262
|
+
return false;
|
|
263
|
+
if (tailSegments.length >= 2) {
|
|
264
|
+
const hasStrongTokenMatch = tailSegments.some((segment) => segment.length >= 5 && corpus.includes(segment));
|
|
265
|
+
if (!hasStrongTokenMatch)
|
|
266
|
+
return false;
|
|
192
267
|
}
|
|
193
|
-
return
|
|
268
|
+
return true;
|
|
194
269
|
};
|
|
195
270
|
const readJsonSafe = (raw, fallback) => {
|
|
196
271
|
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
@@ -417,33 +492,33 @@ export class TaskSufficiencyService {
|
|
|
417
492
|
return items;
|
|
418
493
|
}
|
|
419
494
|
bundleGapItems(gapItems, limit) {
|
|
420
|
-
const groups = new Map();
|
|
421
|
-
const orderedKeys = [];
|
|
422
|
-
for (const item of gapItems) {
|
|
423
|
-
const key = `${item.domain}:${item.kind}`;
|
|
424
|
-
if (!groups.has(key)) {
|
|
425
|
-
groups.set(key, []);
|
|
426
|
-
orderedKeys.push(key);
|
|
427
|
-
}
|
|
428
|
-
groups.get(key)?.push(item);
|
|
429
|
-
}
|
|
430
495
|
const bundles = [];
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
496
|
+
const activeByDomain = new Map();
|
|
497
|
+
for (const item of gapItems.slice(0, limit)) {
|
|
498
|
+
const domainKey = item.domain;
|
|
499
|
+
let bundle = activeByDomain.get(domainKey);
|
|
500
|
+
if (!bundle || bundle.values.length >= GAP_BUNDLE_MAX_ANCHORS) {
|
|
501
|
+
bundle = {
|
|
502
|
+
kind: item.kind,
|
|
503
|
+
domain: item.domain,
|
|
504
|
+
values: [],
|
|
505
|
+
normalizedAnchors: [],
|
|
506
|
+
};
|
|
507
|
+
bundles.push(bundle);
|
|
508
|
+
activeByDomain.set(domainKey, bundle);
|
|
509
|
+
}
|
|
510
|
+
else if (bundle.kind !== item.kind) {
|
|
511
|
+
bundle.kind = "mixed";
|
|
444
512
|
}
|
|
513
|
+
if (!bundle.values.includes(item.value))
|
|
514
|
+
bundle.values.push(item.value);
|
|
515
|
+
if (!bundle.normalizedAnchors.includes(item.normalizedAnchor)) {
|
|
516
|
+
bundle.normalizedAnchors.push(item.normalizedAnchor);
|
|
517
|
+
}
|
|
518
|
+
if (bundles.length >= limit)
|
|
519
|
+
break;
|
|
445
520
|
}
|
|
446
|
-
return bundles;
|
|
521
|
+
return bundles.slice(0, limit);
|
|
447
522
|
}
|
|
448
523
|
async ensureTargetStory(project) {
|
|
449
524
|
const db = this.workspaceRepo.getDb();
|
|
@@ -530,45 +605,59 @@ export class TaskSufficiencyService {
|
|
|
530
605
|
const taskKeyGen = createTaskKeyGenerator(params.storyKey, existingTaskKeys);
|
|
531
606
|
const now = new Date().toISOString();
|
|
532
607
|
const taskInserts = params.gapBundles.map((bundle, index) => {
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
608
|
+
const target = bundle.values[0] ?? "SDS coverage gap";
|
|
609
|
+
const scopeCount = bundle.values.length;
|
|
610
|
+
const domainLabel = bundle.domain.replace(/[-_]+/g, " ").trim() || "coverage";
|
|
611
|
+
const titlePrefix = bundle.kind === "folder"
|
|
612
|
+
? "Implement SDS path"
|
|
613
|
+
: bundle.kind === "mixed"
|
|
614
|
+
? "Implement SDS bundle"
|
|
615
|
+
: "Implement SDS section";
|
|
616
|
+
const title = scopeCount <= 1
|
|
617
|
+
? `${titlePrefix}: ${target}`.slice(0, 180)
|
|
618
|
+
: `Implement SDS ${domainLabel} coverage bundle (${scopeCount} anchors)`.slice(0, 180);
|
|
540
619
|
const objective = bundle.kind === "folder"
|
|
541
|
-
?
|
|
542
|
-
|
|
620
|
+
? scopeCount <= 1
|
|
621
|
+
? `Create or update production code under the SDS folder-tree path \`${target}\`.`
|
|
622
|
+
: `Create or update production code for ${scopeCount} related SDS folder-tree paths in the ${domainLabel} domain.`
|
|
623
|
+
: bundle.kind === "mixed"
|
|
624
|
+
? `Implement a cohesive capability slice covering both SDS sections and folder targets in the ${domainLabel} domain.`
|
|
625
|
+
: scopeCount <= 1
|
|
626
|
+
? `Implement the missing production functionality described by SDS section \`${target}\`.`
|
|
627
|
+
: `Implement ${scopeCount} related SDS section requirements in the ${domainLabel} domain.`;
|
|
543
628
|
const scopeLines = bundle.values.map((value) => `- ${value}`);
|
|
544
629
|
const anchorLines = bundle.normalizedAnchors.map((anchor) => `- ${anchor}`);
|
|
545
630
|
const description = [
|
|
546
|
-
|
|
631
|
+
"## Objective",
|
|
547
632
|
objective,
|
|
548
|
-
|
|
549
|
-
|
|
633
|
+
"",
|
|
634
|
+
"## Context",
|
|
550
635
|
`- Generated by task-sufficiency-audit iteration ${params.iteration}.`,
|
|
551
636
|
`- Coverage domain: ${bundle.domain}`,
|
|
552
|
-
|
|
553
|
-
|
|
637
|
+
`- Scope kind: ${bundle.kind}`,
|
|
638
|
+
"",
|
|
639
|
+
"## Anchor Scope",
|
|
554
640
|
...scopeLines,
|
|
555
|
-
|
|
556
|
-
|
|
641
|
+
"",
|
|
642
|
+
"## Anchor Keys",
|
|
557
643
|
...anchorLines,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
644
|
+
"",
|
|
645
|
+
"## Implementation Plan",
|
|
646
|
+
"- Phase 1: add or update contracts/interfaces used by this scope.",
|
|
647
|
+
"- Phase 2: implement production logic for each anchor item (no docs-only closure).",
|
|
648
|
+
"- Phase 3: wire dependencies and startup/runtime integration points affected by this scope.",
|
|
649
|
+
"- Keep implementation traceable to anchor keys in commit and test evidence.",
|
|
650
|
+
"",
|
|
651
|
+
"## Testing",
|
|
652
|
+
"- Add or update targeted unit/component/integration/API coverage for all listed anchors.",
|
|
653
|
+
"- Execute the smallest deterministic test scope that proves the behavior.",
|
|
654
|
+
"",
|
|
655
|
+
"## Definition of Done",
|
|
656
|
+
"- All anchor scope items in this bundle are represented in working code.",
|
|
657
|
+
"- Validation evidence exists and maps back to each anchor key.",
|
|
571
658
|
].join("\n");
|
|
659
|
+
const storyPointsBase = bundle.kind === "folder" ? 1 : 2;
|
|
660
|
+
const storyPoints = Math.min(8, Math.max(2, storyPointsBase + scopeCount + (bundle.kind === "mixed" ? 1 : 0)));
|
|
572
661
|
return {
|
|
573
662
|
projectId: params.project.id,
|
|
574
663
|
epicId: params.epicId,
|
|
@@ -578,13 +667,14 @@ export class TaskSufficiencyService {
|
|
|
578
667
|
description,
|
|
579
668
|
type: "feature",
|
|
580
669
|
status: "not_started",
|
|
581
|
-
storyPoints
|
|
670
|
+
storyPoints,
|
|
582
671
|
priority: params.maxPriority + index + 1,
|
|
583
672
|
metadata: {
|
|
584
673
|
sufficiencyAudit: {
|
|
585
674
|
source: "task-sufficiency-audit",
|
|
586
675
|
kind: bundle.kind,
|
|
587
676
|
domain: bundle.domain,
|
|
677
|
+
scopeCount,
|
|
588
678
|
values: bundle.values,
|
|
589
679
|
anchor: bundle.normalizedAnchors[0],
|
|
590
680
|
anchors: bundle.normalizedAnchors,
|
|
@@ -670,8 +760,9 @@ export class TaskSufficiencyService {
|
|
|
670
760
|
if (skippedHeadingSignals > 0 || skippedFolderSignals > 0) {
|
|
671
761
|
warnings.push(`Filtered non-actionable SDS signals (headings=${skippedHeadingSignals}, folders=${skippedFolderSignals}) before remediation.`);
|
|
672
762
|
}
|
|
673
|
-
|
|
674
|
-
|
|
763
|
+
const noActionableSignals = sectionHeadings.length === 0 && folderEntries.length === 0;
|
|
764
|
+
if (noActionableSignals) {
|
|
765
|
+
warnings.push("No actionable implementation signals detected from SDS headings/folder tree after filtering.");
|
|
675
766
|
}
|
|
676
767
|
await this.jobService.writeCheckpoint(job.id, {
|
|
677
768
|
stage: "sds_loaded",
|
|
@@ -687,6 +778,9 @@ export class TaskSufficiencyService {
|
|
|
687
778
|
docs: sdsDocs.map((doc) => path.relative(request.workspace.workspaceRoot, doc.path)),
|
|
688
779
|
},
|
|
689
780
|
});
|
|
781
|
+
if (noActionableSignals) {
|
|
782
|
+
throw new Error("task-sufficiency-audit could not derive actionable SDS implementation signals after filtering. Expand SDS implementation headings/folder tree and retry.");
|
|
783
|
+
}
|
|
690
784
|
const iterations = [];
|
|
691
785
|
let totalTasksAdded = 0;
|
|
692
786
|
const totalTasksUpdated = 0;
|
|
@@ -720,7 +814,7 @@ export class TaskSufficiencyService {
|
|
|
720
814
|
});
|
|
721
815
|
break;
|
|
722
816
|
}
|
|
723
|
-
const gapItems = this.buildGapItems(coverage, snapshot.existingAnchors, maxTasksPerIteration
|
|
817
|
+
const gapItems = this.buildGapItems(coverage, snapshot.existingAnchors, maxTasksPerIteration);
|
|
724
818
|
const gapBundles = this.bundleGapItems(gapItems, maxTasksPerIteration);
|
|
725
819
|
if (gapBundles.length === 0) {
|
|
726
820
|
warnings.push(`Iteration ${iteration}: unresolved SDS gaps remain but no insertable gap items were identified.`);
|
|
@@ -812,6 +906,7 @@ export class TaskSufficiencyService {
|
|
|
812
906
|
projectKey: request.projectKey,
|
|
813
907
|
sourceCommand,
|
|
814
908
|
generatedAt: new Date().toISOString(),
|
|
909
|
+
coverageHeuristicsVersion: COVERAGE_HEURISTICS_VERSION,
|
|
815
910
|
dryRun,
|
|
816
911
|
maxIterations,
|
|
817
912
|
maxTasksPerIteration,
|
|
@@ -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;AAyHrE,MAAM,WAAW,iBAAkB,SAAQ,oBAAoB;IAC7D,SAAS,EAAE,mBAAmB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,aAAa,GAAG,aAAa,GAAG,wBAAwB,CAAC;IAClF,uBAAuB,CAAC,EAAE,aAAa,GAAG,UAAU,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAUD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,GAAG,mBAAmB,GAAG,OAAO,GAAG,WAAW,CAAC;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAC/G;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"CodeReviewService.d.ts","sourceRoot":"","sources":["../../../src/services/review/CodeReviewService.ts"],"names":[],"mappings":"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"}
|
|
@@ -207,6 +207,90 @@ const normalizeSlugList = (input) => {
|
|
|
207
207
|
}
|
|
208
208
|
return Array.from(cleaned);
|
|
209
209
|
};
|
|
210
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
211
|
+
const normalizeFailoverEvents = (value) => {
|
|
212
|
+
if (!Array.isArray(value))
|
|
213
|
+
return [];
|
|
214
|
+
const events = [];
|
|
215
|
+
for (const entry of value) {
|
|
216
|
+
if (!isRecord(entry))
|
|
217
|
+
continue;
|
|
218
|
+
if (typeof entry.type !== "string" || entry.type.trim().length === 0)
|
|
219
|
+
continue;
|
|
220
|
+
events.push({ ...entry });
|
|
221
|
+
}
|
|
222
|
+
return events;
|
|
223
|
+
};
|
|
224
|
+
const mergeFailoverEvents = (left, right) => {
|
|
225
|
+
if (!left.length)
|
|
226
|
+
return right;
|
|
227
|
+
if (!right.length)
|
|
228
|
+
return left;
|
|
229
|
+
const seen = new Set();
|
|
230
|
+
const merged = [];
|
|
231
|
+
const signature = (event) => [
|
|
232
|
+
event.type ?? "",
|
|
233
|
+
event.fromAgentId ?? "",
|
|
234
|
+
event.toAgentId ?? "",
|
|
235
|
+
event.at ?? "",
|
|
236
|
+
event.until ?? "",
|
|
237
|
+
event.durationMs ?? "",
|
|
238
|
+
].join("|");
|
|
239
|
+
for (const event of [...left, ...right]) {
|
|
240
|
+
const key = signature(event);
|
|
241
|
+
if (seen.has(key))
|
|
242
|
+
continue;
|
|
243
|
+
seen.add(key);
|
|
244
|
+
merged.push(event);
|
|
245
|
+
}
|
|
246
|
+
return merged;
|
|
247
|
+
};
|
|
248
|
+
const mergeInvocationMetadata = (current, incoming) => {
|
|
249
|
+
if (!current && !incoming)
|
|
250
|
+
return undefined;
|
|
251
|
+
if (!incoming)
|
|
252
|
+
return current;
|
|
253
|
+
if (!current)
|
|
254
|
+
return { ...incoming };
|
|
255
|
+
const merged = { ...current, ...incoming };
|
|
256
|
+
const currentEvents = normalizeFailoverEvents(current.failoverEvents);
|
|
257
|
+
const incomingEvents = normalizeFailoverEvents(incoming.failoverEvents);
|
|
258
|
+
if (currentEvents.length > 0 || incomingEvents.length > 0) {
|
|
259
|
+
merged.failoverEvents = mergeFailoverEvents(currentEvents, incomingEvents);
|
|
260
|
+
}
|
|
261
|
+
return merged;
|
|
262
|
+
};
|
|
263
|
+
const summarizeFailoverEvent = (event) => {
|
|
264
|
+
const type = String(event.type ?? "unknown");
|
|
265
|
+
if (type === "switch_agent") {
|
|
266
|
+
const from = typeof event.fromAgentId === "string" ? event.fromAgentId : "unknown";
|
|
267
|
+
const to = typeof event.toAgentId === "string" ? event.toAgentId : "unknown";
|
|
268
|
+
return `switch_agent ${from} -> ${to}`;
|
|
269
|
+
}
|
|
270
|
+
if (type === "sleep_until_reset") {
|
|
271
|
+
const duration = typeof event.durationMs === "number" && Number.isFinite(event.durationMs)
|
|
272
|
+
? `${Math.round(event.durationMs / 1000)}s`
|
|
273
|
+
: "unknown duration";
|
|
274
|
+
const until = typeof event.until === "string" ? event.until : "unknown";
|
|
275
|
+
return `sleep_until_reset ${duration} (until ${until})`;
|
|
276
|
+
}
|
|
277
|
+
if (type === "stream_restart_after_limit") {
|
|
278
|
+
const from = typeof event.fromAgentId === "string" ? event.fromAgentId : "unknown";
|
|
279
|
+
return `stream_restart_after_limit from ${from}`;
|
|
280
|
+
}
|
|
281
|
+
return type;
|
|
282
|
+
};
|
|
283
|
+
const resolveFailoverAgentId = (events, fallbackAgentId) => {
|
|
284
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
285
|
+
const event = events[index];
|
|
286
|
+
if (event?.type !== "switch_agent")
|
|
287
|
+
continue;
|
|
288
|
+
if (typeof event.toAgentId === "string" && event.toAgentId.trim().length > 0) {
|
|
289
|
+
return event.toAgentId;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return fallbackAgentId;
|
|
293
|
+
};
|
|
210
294
|
const normalizePath = (value) => value
|
|
211
295
|
.replace(/\\/g, "/")
|
|
212
296
|
.replace(/^\.\//, "")
|
|
@@ -1990,6 +2074,45 @@ export class CodeReviewService {
|
|
|
1990
2074
|
metadata: { commandName: "code-review", phase, action: phase, attempt },
|
|
1991
2075
|
});
|
|
1992
2076
|
};
|
|
2077
|
+
const logFailoverEvents = async (events) => {
|
|
2078
|
+
if (!events.length)
|
|
2079
|
+
return;
|
|
2080
|
+
for (const event of events) {
|
|
2081
|
+
await this.deps.workspaceRepo.insertTaskLog({
|
|
2082
|
+
taskRunId: taskRun.id,
|
|
2083
|
+
sequence: this.sequenceForTask(taskRun.id),
|
|
2084
|
+
timestamp: new Date().toISOString(),
|
|
2085
|
+
source: "agent_failover",
|
|
2086
|
+
message: `Agent failover: ${summarizeFailoverEvent(event)}`,
|
|
2087
|
+
details: event,
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
const resolveUsageAgent = async (fallback, events) => {
|
|
2092
|
+
const usageAgentId = resolveFailoverAgentId(events, fallback.id);
|
|
2093
|
+
if (usageAgentId === fallback.id)
|
|
2094
|
+
return fallback;
|
|
2095
|
+
const resolver = this.deps.agentService?.resolveAgent;
|
|
2096
|
+
if (typeof resolver !== "function")
|
|
2097
|
+
return fallback;
|
|
2098
|
+
try {
|
|
2099
|
+
const resolved = await resolver.call(this.deps.agentService, usageAgentId);
|
|
2100
|
+
return {
|
|
2101
|
+
id: resolved.id,
|
|
2102
|
+
defaultModel: typeof resolved.defaultModel === "string" ? resolved.defaultModel : fallback.defaultModel,
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
catch (error) {
|
|
2106
|
+
await this.deps.workspaceRepo.insertTaskLog({
|
|
2107
|
+
taskRunId: taskRun.id,
|
|
2108
|
+
sequence: this.sequenceForTask(taskRun.id),
|
|
2109
|
+
timestamp: new Date().toISOString(),
|
|
2110
|
+
source: "agent_failover",
|
|
2111
|
+
message: `Unable to resolve failover agent (${usageAgentId}) for usage accounting: ${error instanceof Error ? error.message : String(error)}`,
|
|
2112
|
+
});
|
|
2113
|
+
return fallback;
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
1993
2116
|
agentOutput = "";
|
|
1994
2117
|
let durationSeconds = 0;
|
|
1995
2118
|
let lastStreamMeta;
|
|
@@ -2002,7 +2125,7 @@ export class CodeReviewService {
|
|
|
2002
2125
|
if (useStream && this.deps.agentService.invokeStream) {
|
|
2003
2126
|
const stream = await withAbort(this.deps.agentService.invokeStream(agentToUse.id, {
|
|
2004
2127
|
input: prompt,
|
|
2005
|
-
metadata: { taskKey: task.key, retry: logSource === "agent_retry" },
|
|
2128
|
+
metadata: { command: "code-review", taskKey: task.key, retry: logSource === "agent_retry" },
|
|
2006
2129
|
}));
|
|
2007
2130
|
while (true) {
|
|
2008
2131
|
abortIfSignaled();
|
|
@@ -2011,7 +2134,7 @@ export class CodeReviewService {
|
|
|
2011
2134
|
break;
|
|
2012
2135
|
const chunk = value;
|
|
2013
2136
|
output += chunk.output ?? "";
|
|
2014
|
-
metadata = chunk.metadata
|
|
2137
|
+
metadata = mergeInvocationMetadata(metadata, chunk.metadata);
|
|
2015
2138
|
await this.deps.workspaceRepo.insertTaskLog({
|
|
2016
2139
|
taskRunId: taskRun.id,
|
|
2017
2140
|
sequence: this.sequenceForTask(taskRun.id),
|
|
@@ -2024,10 +2147,10 @@ export class CodeReviewService {
|
|
|
2024
2147
|
else {
|
|
2025
2148
|
const response = await withAbort(this.deps.agentService.invoke(agentToUse.id, {
|
|
2026
2149
|
input: prompt,
|
|
2027
|
-
metadata: { taskKey: task.key, retry: logSource === "agent_retry" },
|
|
2150
|
+
metadata: { command: "code-review", taskKey: task.key, retry: logSource === "agent_retry" },
|
|
2028
2151
|
}));
|
|
2029
2152
|
output = response.output ?? "";
|
|
2030
|
-
metadata = response.metadata;
|
|
2153
|
+
metadata = mergeInvocationMetadata(metadata, response.metadata);
|
|
2031
2154
|
await this.deps.workspaceRepo.insertTaskLog({
|
|
2032
2155
|
taskRunId: taskRun.id,
|
|
2033
2156
|
sequence: this.sequenceForTask(taskRun.id),
|
|
@@ -2074,7 +2197,15 @@ export class CodeReviewService {
|
|
|
2074
2197
|
model: (lastStreamMeta.model ?? lastStreamMeta.model_name ?? null),
|
|
2075
2198
|
}
|
|
2076
2199
|
: undefined;
|
|
2077
|
-
|
|
2200
|
+
const mainFailoverEvents = normalizeFailoverEvents(lastStreamMeta?.failoverEvents);
|
|
2201
|
+
await logFailoverEvents(mainFailoverEvents);
|
|
2202
|
+
const usageAgentForOutput = await resolveUsageAgent({
|
|
2203
|
+
id: agentUsedForOutput.id,
|
|
2204
|
+
defaultModel: typeof agentUsedForOutput?.defaultModel === "string"
|
|
2205
|
+
? agentUsedForOutput.defaultModel
|
|
2206
|
+
: undefined,
|
|
2207
|
+
}, mainFailoverEvents);
|
|
2208
|
+
await recordUsage("review_main", prompt, agentOutput, durationSeconds, tokenMetaMain, usageAgentForOutput, outputAttempt);
|
|
2078
2209
|
const primaryOutput = agentOutput;
|
|
2079
2210
|
let retryOutput;
|
|
2080
2211
|
let retryAgentUsed;
|
|
@@ -2126,7 +2257,10 @@ export class CodeReviewService {
|
|
|
2126
2257
|
message: `Retrying with JSON-only agent override: ${retryAgentUsed.slug ?? retryAgentUsed.id}`,
|
|
2127
2258
|
});
|
|
2128
2259
|
}
|
|
2129
|
-
const retryResp = await withAbort(this.deps.agentService.invoke(retryAgentUsed.id, {
|
|
2260
|
+
const retryResp = await withAbort(this.deps.agentService.invoke(retryAgentUsed.id, {
|
|
2261
|
+
input: retryPrompt,
|
|
2262
|
+
metadata: { command: "code-review", taskKey: task.key, retry: true },
|
|
2263
|
+
}));
|
|
2130
2264
|
retryOutput = retryResp.output ?? "";
|
|
2131
2265
|
const retryDuration = Math.round(((Date.now() - retryStarted) / 1000) * 1000) / 1000;
|
|
2132
2266
|
await this.deps.workspaceRepo.insertTaskLog({
|
|
@@ -2150,7 +2284,15 @@ export class CodeReviewService {
|
|
|
2150
2284
|
model: (retryResp.metadata.model ?? retryResp.metadata.model_name ?? null),
|
|
2151
2285
|
}
|
|
2152
2286
|
: undefined;
|
|
2153
|
-
|
|
2287
|
+
const retryFailoverEvents = normalizeFailoverEvents(retryResp.metadata?.failoverEvents);
|
|
2288
|
+
await logFailoverEvents(retryFailoverEvents);
|
|
2289
|
+
const retryUsageAgent = await resolveUsageAgent({
|
|
2290
|
+
id: retryAgentUsed.id,
|
|
2291
|
+
defaultModel: typeof retryAgentUsed?.defaultModel === "string"
|
|
2292
|
+
? retryAgentUsed.defaultModel
|
|
2293
|
+
: undefined,
|
|
2294
|
+
}, retryFailoverEvents);
|
|
2295
|
+
await recordUsage("review_retry", retryPrompt, retryOutput, retryDuration, retryTokenMeta, retryUsageAgent, 2);
|
|
2154
2296
|
normalization = normalizeReviewOutput(retryOutput);
|
|
2155
2297
|
parsed = normalization.result;
|
|
2156
2298
|
validationError = validateReviewOutput(parsed, { requireCommentSlugs });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcoda/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Core services and APIs for the mcoda CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
34
34
|
"yaml": "^2.4.2",
|
|
35
|
-
"@mcoda/shared": "0.1.
|
|
36
|
-
"@mcoda/
|
|
37
|
-
"@mcoda/
|
|
38
|
-
"@mcoda/
|
|
39
|
-
"@mcoda/
|
|
35
|
+
"@mcoda/shared": "0.1.28",
|
|
36
|
+
"@mcoda/generators": "0.1.28",
|
|
37
|
+
"@mcoda/db": "0.1.28",
|
|
38
|
+
"@mcoda/agents": "0.1.28",
|
|
39
|
+
"@mcoda/integrations": "0.1.28"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.json",
|