@mcoda/core 0.1.26 → 0.1.27
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/services/planning/CreateTasksService.d.ts +1 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +74 -53
- package/dist/services/planning/TaskSufficiencyService.d.ts +1 -0
- package/dist/services/planning/TaskSufficiencyService.d.ts.map +1 -1
- package/dist/services/planning/TaskSufficiencyService.js +249 -45
- package/package.json +6 -6
|
@@ -92,6 +92,7 @@ export declare class CreateTasksService {
|
|
|
92
92
|
private sortServicesByDependency;
|
|
93
93
|
private buildServiceDependencyGraph;
|
|
94
94
|
private buildProjectConstructionMethod;
|
|
95
|
+
private buildProjectPlanArtifact;
|
|
95
96
|
private orderStoryTasksByDependencies;
|
|
96
97
|
private applyServiceDependencySequencing;
|
|
97
98
|
private shouldInjectStructureBootstrap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/CreateTasksService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAEL,OAAO,EACP,gBAAgB,EAEhB,QAAQ,EAER,iBAAiB,EAEjB,OAAO,EACP,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAOxE,OAAO,EAAE,sBAAsB,EAAmC,MAAM,6BAA6B,CAAC;AAEtG,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,YAAY,EAAE,iBAAiB,EAAE,CAAC;CACnC;
|
|
1
|
+
{"version":3,"file":"CreateTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/CreateTasksService.ts"],"names":[],"mappings":"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"}
|
|
@@ -398,58 +398,50 @@ const extractJsonObjects = (value) => {
|
|
|
398
398
|
}
|
|
399
399
|
return results;
|
|
400
400
|
};
|
|
401
|
+
const compactNarrative = (value, fallback, maxLines = 5) => {
|
|
402
|
+
if (!value || value.trim().length === 0)
|
|
403
|
+
return fallback;
|
|
404
|
+
const lines = value
|
|
405
|
+
.split(/\r?\n/)
|
|
406
|
+
.map((line) => line
|
|
407
|
+
.replace(/^[*-]\s+/, "")
|
|
408
|
+
.replace(/^#+\s+/, "")
|
|
409
|
+
.replace(/^\*+\s*\*\*(.+?)\*\*\s*$/, "$1")
|
|
410
|
+
.trim())
|
|
411
|
+
.filter(Boolean)
|
|
412
|
+
.filter((line) => !/^(in scope|out of scope|key flows?|non-functional requirements|dependencies|risks|acceptance criteria|related docs?)\s*:/i.test(line))
|
|
413
|
+
.slice(0, maxLines);
|
|
414
|
+
return lines.length > 0 ? lines.join("\n") : fallback;
|
|
415
|
+
};
|
|
401
416
|
const buildEpicDescription = (epicKey, title, description, acceptance, relatedDocs) => {
|
|
417
|
+
const context = compactNarrative(description, `Deliver ${title} with implementation-ready scope and sequencing aligned to SDS guidance.`, 6);
|
|
402
418
|
return [
|
|
403
419
|
`* **Epic Key**: ${epicKey}`,
|
|
404
420
|
`* **Epic Title**: ${title}`,
|
|
405
421
|
"* **Context / Problem**",
|
|
406
422
|
"",
|
|
407
|
-
|
|
408
|
-
"* **Goals & Outcomes**",
|
|
409
|
-
formatBullets(acceptance, "List measurable outcomes for this epic."),
|
|
410
|
-
"* **In Scope**",
|
|
411
|
-
"- Clarify during refinement; derived from RFP/PDR/SDS.",
|
|
412
|
-
"* **Out of Scope**",
|
|
413
|
-
"- To be defined; exclude unrelated systems.",
|
|
414
|
-
"* **Key Flows / Scenarios**",
|
|
415
|
-
"- Outline primary user flows for this epic.",
|
|
416
|
-
"* **Non-functional Requirements**",
|
|
417
|
-
"- Performance, security, reliability expectations go here.",
|
|
418
|
-
"* **Dependencies & Constraints**",
|
|
419
|
-
"- Capture upstream/downstream systems and blockers.",
|
|
420
|
-
"* **Risks & Open Questions**",
|
|
421
|
-
"- Identify risks and unknowns to resolve.",
|
|
423
|
+
context,
|
|
422
424
|
"* **Acceptance Criteria**",
|
|
423
|
-
formatBullets(acceptance, "
|
|
425
|
+
formatBullets(acceptance, "Define measurable and testable outcomes for this epic."),
|
|
424
426
|
"* **Related Documentation / References**",
|
|
425
|
-
formatBullets(relatedDocs, "Link
|
|
427
|
+
formatBullets(relatedDocs, "Link SDS/PDR/OpenAPI references used by this epic."),
|
|
426
428
|
].join("\n");
|
|
427
429
|
};
|
|
428
430
|
const buildStoryDescription = (storyKey, title, userStory, description, acceptanceCriteria, relatedDocs) => {
|
|
431
|
+
const userStoryText = compactNarrative(userStory, `As a user, I want ${title} so that it delivers clear product value.`, 3);
|
|
432
|
+
const contextText = compactNarrative(description, `Implement ${title} with concrete scope and dependency context.`, 5);
|
|
429
433
|
return [
|
|
430
434
|
`* **Story Key**: ${storyKey}`,
|
|
431
435
|
"* **User Story**",
|
|
432
436
|
"",
|
|
433
|
-
|
|
437
|
+
userStoryText,
|
|
434
438
|
"* **Context**",
|
|
435
439
|
"",
|
|
436
|
-
|
|
437
|
-
"* **Preconditions / Assumptions**",
|
|
438
|
-
"- Confirm required data, environments, and access.",
|
|
439
|
-
"* **Main Flow**",
|
|
440
|
-
"- Outline the happy path for this story.",
|
|
441
|
-
"* **Alternative / Error Flows**",
|
|
442
|
-
"- Capture error handling and non-happy paths.",
|
|
443
|
-
"* **UX / UI Notes**",
|
|
444
|
-
"- Enumerate screens/states if applicable.",
|
|
445
|
-
"* **Data & Integrations**",
|
|
446
|
-
"- Note key entities, APIs, queues, or third-party dependencies.",
|
|
440
|
+
contextText,
|
|
447
441
|
"* **Acceptance Criteria**",
|
|
448
442
|
formatBullets(acceptanceCriteria, "List testable outcomes for this story."),
|
|
449
|
-
"* **Non-functional Requirements**",
|
|
450
|
-
"- Add story-specific performance/reliability/security expectations.",
|
|
451
443
|
"* **Related Documentation / References**",
|
|
452
|
-
formatBullets(relatedDocs, "Docdex handles, OpenAPI endpoints, code modules."),
|
|
444
|
+
formatBullets(relatedDocs, "Docdex handles, OpenAPI endpoints, and code modules."),
|
|
453
445
|
].join("\n");
|
|
454
446
|
};
|
|
455
447
|
const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, relatedDocs, dependencies, tests, qa) => {
|
|
@@ -463,7 +455,7 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
463
455
|
})
|
|
464
456
|
.join("\n");
|
|
465
457
|
};
|
|
466
|
-
const objectiveText =
|
|
458
|
+
const objectiveText = compactNarrative(description, `Deliver ${title} for story ${storyKey}.`, 3);
|
|
467
459
|
const implementationLines = extractActionableLines(description, 4);
|
|
468
460
|
const riskLines = extractRiskLines(description, 3);
|
|
469
461
|
const testsDefined = (tests.unitTests?.length ?? 0) +
|
|
@@ -482,14 +474,14 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
482
474
|
qa?.blockers?.length ? "- Remaining QA blockers are explicit and actionable." : "- QA blockers are resolved or not present.",
|
|
483
475
|
];
|
|
484
476
|
const defaultImplementationPlan = [
|
|
485
|
-
|
|
477
|
+
`Implement ${title} with concrete file/module-level changes aligned to the objective.`,
|
|
486
478
|
dependencies.length
|
|
487
|
-
?
|
|
488
|
-
: "
|
|
479
|
+
? `Respect dependency order before completion: ${dependencies.join(", ")}.`
|
|
480
|
+
: "Finalize concrete implementation steps before coding and keep scope bounded.",
|
|
489
481
|
];
|
|
490
482
|
const defaultRisks = dependencies.length
|
|
491
|
-
? [
|
|
492
|
-
: ["
|
|
483
|
+
? [`Delivery depends on upstream tasks: ${dependencies.join(", ")}.`]
|
|
484
|
+
: ["Keep implementation aligned to SDS/OpenAPI contracts to avoid drift."];
|
|
493
485
|
return [
|
|
494
486
|
`* **Task Key**: ${taskKey}`,
|
|
495
487
|
"* **Objective**",
|
|
@@ -500,7 +492,7 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
500
492
|
`- Epic: ${epicKey}`,
|
|
501
493
|
`- Story: ${storyKey}`,
|
|
502
494
|
"* **Inputs**",
|
|
503
|
-
formatBullets(relatedDocs, "
|
|
495
|
+
formatBullets(relatedDocs, "No explicit external references."),
|
|
504
496
|
"* **Implementation Plan**",
|
|
505
497
|
formatBullets(implementationLines, defaultImplementationPlan.join(" ")),
|
|
506
498
|
"* **Definition of Done**",
|
|
@@ -519,11 +511,11 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
519
511
|
"* **QA Blockers**",
|
|
520
512
|
formatBullets(qa?.blockers, "None known."),
|
|
521
513
|
"* **Dependencies**",
|
|
522
|
-
formatBullets(dependencies, "
|
|
514
|
+
formatBullets(dependencies, "None."),
|
|
523
515
|
"* **Risks & Gotchas**",
|
|
524
516
|
formatBullets(riskLines, defaultRisks.join(" ")),
|
|
525
517
|
"* **Related Documentation / References**",
|
|
526
|
-
formatBullets(relatedDocs, "
|
|
518
|
+
formatBullets(relatedDocs, "None."),
|
|
527
519
|
].join("\n");
|
|
528
520
|
};
|
|
529
521
|
const collectFilesRecursively = async (target) => {
|
|
@@ -1489,6 +1481,21 @@ export class CreateTasksService {
|
|
|
1489
1481
|
"5) Keep task dependencies story-scoped while preserving epic/story/task ordering by this build method.",
|
|
1490
1482
|
].join("\n");
|
|
1491
1483
|
}
|
|
1484
|
+
buildProjectPlanArtifact(projectKey, docs, graph, buildMethod) {
|
|
1485
|
+
const sourceDocs = docs
|
|
1486
|
+
.map((doc) => doc.path ?? (doc.id ? `docdex:${doc.id}` : doc.title ?? "doc"))
|
|
1487
|
+
.filter((value) => Boolean(value))
|
|
1488
|
+
.slice(0, 24);
|
|
1489
|
+
return {
|
|
1490
|
+
projectKey,
|
|
1491
|
+
generatedAt: new Date().toISOString(),
|
|
1492
|
+
sourceDocs,
|
|
1493
|
+
startupWaves: graph.startupWaves.slice(0, 12),
|
|
1494
|
+
services: graph.services.slice(0, 40),
|
|
1495
|
+
foundationalDependencies: graph.foundationalDependencies.slice(0, 16),
|
|
1496
|
+
buildMethod,
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1492
1499
|
orderStoryTasksByDependencies(storyTasks, serviceRank, taskServiceByScope) {
|
|
1493
1500
|
const byLocalId = new Map(storyTasks.map((task) => [task.localId, task]));
|
|
1494
1501
|
const indegree = new Map();
|
|
@@ -2187,17 +2194,18 @@ export class CreateTasksService {
|
|
|
2187
2194
|
.filter(Boolean)
|
|
2188
2195
|
.join(" ");
|
|
2189
2196
|
const prompt = [
|
|
2190
|
-
`You are assisting in
|
|
2191
|
-
"
|
|
2192
|
-
"
|
|
2197
|
+
`You are assisting in phase 1 of 3 for project ${projectKey}: generate epics only.`,
|
|
2198
|
+
"Process is strict and direct: build plan -> epics -> stories -> tasks.",
|
|
2199
|
+
"This step outputs only epics derived from the build plan and docs.",
|
|
2193
2200
|
"Return strictly valid JSON (no prose) matching:",
|
|
2194
2201
|
EPIC_SCHEMA_SNIPPET,
|
|
2195
2202
|
"Rules:",
|
|
2196
2203
|
"- Do NOT include final slugs; the system will assign keys.",
|
|
2197
2204
|
"- Use docdex handles when referencing docs.",
|
|
2198
2205
|
"- acceptanceCriteria must be an array of strings (5-10 items).",
|
|
2199
|
-
"-
|
|
2200
|
-
"-
|
|
2206
|
+
"- Keep epics actionable and implementation-oriented; avoid glossary/admin-only epics.",
|
|
2207
|
+
"- Prefer dependency-first sequencing: foundational setup epics before dependent feature epics.",
|
|
2208
|
+
"- Keep output derived from docs; do not assume stacks unless docs state them.",
|
|
2201
2209
|
"Project construction method to follow:",
|
|
2202
2210
|
projectBuildMethod,
|
|
2203
2211
|
limits || "Use reasonable scope without over-generating epics.",
|
|
@@ -2453,14 +2461,15 @@ export class CreateTasksService {
|
|
|
2453
2461
|
}
|
|
2454
2462
|
async generateStoriesForEpic(agent, epic, docSummary, projectBuildMethod, stream, jobId, commandRunId) {
|
|
2455
2463
|
const prompt = [
|
|
2456
|
-
`Generate user stories for epic "${epic.title}".`,
|
|
2457
|
-
"
|
|
2464
|
+
`Generate user stories for epic "${epic.title}" (phase 2 of 3).`,
|
|
2465
|
+
"This phase is stories-only. Do not generate tasks yet.",
|
|
2458
2466
|
"Return JSON only matching:",
|
|
2459
2467
|
STORY_SCHEMA_SNIPPET,
|
|
2460
2468
|
"Rules:",
|
|
2461
2469
|
"- No tasks in this step.",
|
|
2462
2470
|
"- acceptanceCriteria must be an array of strings.",
|
|
2463
2471
|
"- Use docdex handles when citing docs.",
|
|
2472
|
+
"- Keep stories direct and implementation-oriented; avoid placeholder-only narrative sections.",
|
|
2464
2473
|
"- Keep story sequencing aligned with the project construction method.",
|
|
2465
2474
|
`Epic context (key=${epic.key ?? epic.localId ?? "TBD"}):`,
|
|
2466
2475
|
epic.description ?? "(no description provided)",
|
|
@@ -2498,8 +2507,8 @@ export class CreateTasksService {
|
|
|
2498
2507
|
.filter(Boolean);
|
|
2499
2508
|
};
|
|
2500
2509
|
const prompt = [
|
|
2501
|
-
`Generate tasks for story "${story.title}" (Epic: ${epic.title}).`,
|
|
2502
|
-
"
|
|
2510
|
+
`Generate tasks for story "${story.title}" (Epic: ${epic.title}, phase 3 of 3).`,
|
|
2511
|
+
"This phase is tasks-only for the given story.",
|
|
2503
2512
|
"Return JSON only matching:",
|
|
2504
2513
|
TASK_SCHEMA_SNIPPET,
|
|
2505
2514
|
"Rules:",
|
|
@@ -2517,6 +2526,7 @@ export class CreateTasksService {
|
|
|
2517
2526
|
"- Keep dependencies strictly inside this story; never reference tasks from other stories/epics.",
|
|
2518
2527
|
"- Order tasks from foundational prerequisites to dependents based on documented dependency direction and startup constraints.",
|
|
2519
2528
|
"- Avoid placeholder wording (TBD, TODO, to be defined, generic follow-up phrases).",
|
|
2529
|
+
"- Avoid documentation-only or glossary-only tasks unless story acceptance explicitly requires them.",
|
|
2520
2530
|
"- Use docdex handles when citing docs.",
|
|
2521
2531
|
"- If OPENAPI_HINTS are present in Docs, align tasks with hinted service/capability/stage/test_requirements.",
|
|
2522
2532
|
"- If SDS_COVERAGE_HINTS are present in Docs, cover the relevant SDS sections in implementation tasks.",
|
|
@@ -2766,14 +2776,15 @@ export class CreateTasksService {
|
|
|
2766
2776
|
: ["Coverage is heading-based heuristic match between SDS sections and generated epic/story/task corpus."],
|
|
2767
2777
|
};
|
|
2768
2778
|
}
|
|
2769
|
-
async writePlanArtifacts(projectKey, plan, docSummary, docs) {
|
|
2779
|
+
async writePlanArtifacts(projectKey, plan, docSummary, docs, buildPlan) {
|
|
2770
2780
|
const baseDir = path.join(this.workspace.mcodaDir, "tasks", projectKey);
|
|
2771
2781
|
await fs.mkdir(baseDir, { recursive: true });
|
|
2772
2782
|
const write = async (file, data) => {
|
|
2773
2783
|
const target = path.join(baseDir, file);
|
|
2774
2784
|
await fs.writeFile(target, JSON.stringify(data, null, 2), "utf8");
|
|
2775
2785
|
};
|
|
2776
|
-
await write("plan.json", { projectKey, generatedAt: new Date().toISOString(), docSummary, ...plan });
|
|
2786
|
+
await write("plan.json", { projectKey, generatedAt: new Date().toISOString(), docSummary, buildPlan, ...plan });
|
|
2787
|
+
await write("build-plan.json", buildPlan);
|
|
2777
2788
|
await write("epics.json", plan.epics);
|
|
2778
2789
|
await write("stories.json", plan.stories);
|
|
2779
2790
|
await write("tasks.json", plan.tasks);
|
|
@@ -3035,6 +3046,7 @@ export class CreateTasksService {
|
|
|
3035
3046
|
const { docSummary, warnings: docWarnings } = this.buildDocContext(docs);
|
|
3036
3047
|
const discoveryGraph = this.buildServiceDependencyGraph({ epics: [], stories: [], tasks: [] }, docs);
|
|
3037
3048
|
const projectBuildMethod = this.buildProjectConstructionMethod(docs, discoveryGraph);
|
|
3049
|
+
const projectBuildPlan = this.buildProjectPlanArtifact(options.projectKey, docs, discoveryGraph, projectBuildMethod);
|
|
3038
3050
|
const { prompt } = this.buildPrompt(options.projectKey, docs, projectBuildMethod, options);
|
|
3039
3051
|
const qaPreflight = await this.buildQaPreflight();
|
|
3040
3052
|
const qaOverrides = this.buildQaOverrides(options);
|
|
@@ -3043,6 +3055,15 @@ export class CreateTasksService {
|
|
|
3043
3055
|
timestamp: new Date().toISOString(),
|
|
3044
3056
|
details: { count: docs.length, warnings: docWarnings, startupWaves: discoveryGraph.startupWaves.slice(0, 8) },
|
|
3045
3057
|
});
|
|
3058
|
+
await this.jobService.writeCheckpoint(job.id, {
|
|
3059
|
+
stage: "build_plan_defined",
|
|
3060
|
+
timestamp: new Date().toISOString(),
|
|
3061
|
+
details: {
|
|
3062
|
+
sourceDocs: projectBuildPlan.sourceDocs.length,
|
|
3063
|
+
services: projectBuildPlan.services.length,
|
|
3064
|
+
startupWaves: projectBuildPlan.startupWaves.length,
|
|
3065
|
+
},
|
|
3066
|
+
});
|
|
3046
3067
|
await this.jobService.writeCheckpoint(job.id, {
|
|
3047
3068
|
stage: "qa_preflight",
|
|
3048
3069
|
timestamp: new Date().toISOString(),
|
|
@@ -3102,7 +3123,7 @@ export class CreateTasksService {
|
|
|
3102
3123
|
timestamp: new Date().toISOString(),
|
|
3103
3124
|
details: { tasks: plan.tasks.length, source: planSource, fallbackReason },
|
|
3104
3125
|
});
|
|
3105
|
-
const { folder } = await this.writePlanArtifacts(options.projectKey, plan, docSummary, docs);
|
|
3126
|
+
const { folder } = await this.writePlanArtifacts(options.projectKey, plan, docSummary, docs, projectBuildPlan);
|
|
3106
3127
|
await this.jobService.writeCheckpoint(job.id, {
|
|
3107
3128
|
stage: "plan_written",
|
|
3108
3129
|
timestamp: new Date().toISOString(),
|
|
@@ -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;AA6EnD,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;AAuJF,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;YA8BR,iBAAiB;YAyGjB,cAAc;YAkHd,oBAAoB;IAkB5B,QAAQ,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC;CAmU1F"}
|
|
@@ -10,10 +10,39 @@ const DEFAULT_MIN_COVERAGE_RATIO = 0.96;
|
|
|
10
10
|
const SDS_SCAN_MAX_FILES = 120;
|
|
11
11
|
const SDS_HEADING_LIMIT = 200;
|
|
12
12
|
const SDS_FOLDER_LIMIT = 240;
|
|
13
|
+
const GAP_BUNDLE_SIZE = 4;
|
|
13
14
|
const REPORT_FILE_NAME = "task-sufficiency-report.json";
|
|
14
15
|
const ignoredDirs = new Set([".git", "node_modules", "dist", "build", ".mcoda", ".docdex"]);
|
|
15
16
|
const sdsFilenamePattern = /(sds|software[-_ ]design|system[-_ ]design|design[-_ ]spec)/i;
|
|
16
17
|
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 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
|
+
const repoRootSegments = new Set([
|
|
21
|
+
"apps",
|
|
22
|
+
"api",
|
|
23
|
+
"backend",
|
|
24
|
+
"config",
|
|
25
|
+
"configs",
|
|
26
|
+
"db",
|
|
27
|
+
"deployment",
|
|
28
|
+
"deployments",
|
|
29
|
+
"docs",
|
|
30
|
+
"frontend",
|
|
31
|
+
"implementation",
|
|
32
|
+
"infra",
|
|
33
|
+
"internal",
|
|
34
|
+
"packages",
|
|
35
|
+
"scripts",
|
|
36
|
+
"service",
|
|
37
|
+
"services",
|
|
38
|
+
"shared",
|
|
39
|
+
"src",
|
|
40
|
+
"test",
|
|
41
|
+
"tests",
|
|
42
|
+
"ui",
|
|
43
|
+
"web",
|
|
44
|
+
]);
|
|
45
|
+
const headingNoiseTokens = new Set(["and", "for", "from", "into", "the", "with"]);
|
|
17
46
|
const normalizeText = (value) => value
|
|
18
47
|
.toLowerCase()
|
|
19
48
|
.replace(/[`*_]/g, " ")
|
|
@@ -22,6 +51,75 @@ const normalizeText = (value) => value
|
|
|
22
51
|
.trim();
|
|
23
52
|
const normalizeAnchor = (kind, value) => `${kind}:${normalizeText(value).replace(/\s+/g, " ").trim()}`;
|
|
24
53
|
const unique = (items) => Array.from(new Set(items.filter(Boolean)));
|
|
54
|
+
const stripDecorators = (value) => value
|
|
55
|
+
.replace(/[`*_]/g, " ")
|
|
56
|
+
.replace(/^[\s>:\-[\]().]+/, "")
|
|
57
|
+
.replace(/\s+/g, " ")
|
|
58
|
+
.trim();
|
|
59
|
+
const normalizeHeadingCandidate = (value) => {
|
|
60
|
+
const cleaned = stripDecorators(value).replace(/^\d+(?:\.\d+)*\s+/, "").trim();
|
|
61
|
+
return cleaned.length > 0 ? cleaned : stripDecorators(value);
|
|
62
|
+
};
|
|
63
|
+
const headingLooksImplementationRelevant = (heading) => {
|
|
64
|
+
const normalized = normalizeHeadingCandidate(heading).toLowerCase();
|
|
65
|
+
if (!normalized || normalized.length < 3)
|
|
66
|
+
return false;
|
|
67
|
+
if (nonImplementationHeadingPattern.test(normalized))
|
|
68
|
+
return false;
|
|
69
|
+
if (likelyImplementationHeadingPattern.test(normalized))
|
|
70
|
+
return true;
|
|
71
|
+
const sectionMatch = heading.trim().match(/^(\d+)(?:\.\d+)*(?:\s+|$)/);
|
|
72
|
+
if (sectionMatch) {
|
|
73
|
+
const major = Number.parseInt(sectionMatch[1] ?? "", 10);
|
|
74
|
+
if (Number.isFinite(major) && major >= 3)
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
const tokens = normalized
|
|
78
|
+
.split(/\s+/)
|
|
79
|
+
.map((token) => token.replace(/[^a-z0-9.-]+/g, ""))
|
|
80
|
+
.filter((token) => token.length >= 4 && !headingNoiseTokens.has(token));
|
|
81
|
+
return tokens.length >= 2;
|
|
82
|
+
};
|
|
83
|
+
const normalizeFolderEntry = (entry) => {
|
|
84
|
+
const trimmed = stripDecorators(entry)
|
|
85
|
+
.replace(/^\.?\//, "")
|
|
86
|
+
.replace(/\/+$/, "")
|
|
87
|
+
.replace(/\s+/g, "");
|
|
88
|
+
if (!trimmed.includes("/"))
|
|
89
|
+
return undefined;
|
|
90
|
+
if (trimmed.includes("...") || trimmed.includes("*"))
|
|
91
|
+
return undefined;
|
|
92
|
+
return trimmed;
|
|
93
|
+
};
|
|
94
|
+
const folderEntryLooksRepoRelevant = (entry) => {
|
|
95
|
+
const normalized = normalizeFolderEntry(entry);
|
|
96
|
+
if (!normalized)
|
|
97
|
+
return false;
|
|
98
|
+
if (normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized))
|
|
99
|
+
return false;
|
|
100
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
101
|
+
if (segments.length < 2)
|
|
102
|
+
return false;
|
|
103
|
+
const root = segments[0].toLowerCase();
|
|
104
|
+
return repoRootSegments.has(root);
|
|
105
|
+
};
|
|
106
|
+
const deriveSectionDomain = (heading) => {
|
|
107
|
+
const normalized = normalizeHeadingCandidate(heading).toLowerCase();
|
|
108
|
+
const tokens = normalized
|
|
109
|
+
.split(/\s+/)
|
|
110
|
+
.map((token) => token.replace(/[^a-z0-9.-]+/g, ""))
|
|
111
|
+
.filter((token) => token.length >= 3 && !headingNoiseTokens.has(token));
|
|
112
|
+
return tokens[0] ?? "coverage";
|
|
113
|
+
};
|
|
114
|
+
const deriveFolderDomain = (entry) => {
|
|
115
|
+
const normalized = normalizeFolderEntry(entry)?.toLowerCase();
|
|
116
|
+
if (!normalized)
|
|
117
|
+
return "structure";
|
|
118
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
119
|
+
if (segments.length === 0)
|
|
120
|
+
return "structure";
|
|
121
|
+
return segments.length === 1 ? segments[0] : `${segments[0]}-${segments[1]}`;
|
|
122
|
+
};
|
|
25
123
|
const extractMarkdownHeadings = (content, limit) => {
|
|
26
124
|
if (!content)
|
|
27
125
|
return [];
|
|
@@ -241,10 +339,19 @@ export class TaskSufficiencyService {
|
|
|
241
339
|
for (const task of tasks) {
|
|
242
340
|
corpusChunks.push(`${task.title ?? ""} ${task.description ?? ""}`);
|
|
243
341
|
const metadata = readJsonSafe(task.metadata_json, null);
|
|
244
|
-
const
|
|
342
|
+
const sufficiencyAudit = metadata?.sufficiencyAudit;
|
|
343
|
+
const rawAnchor = sufficiencyAudit?.anchor;
|
|
245
344
|
if (typeof rawAnchor === "string" && rawAnchor.trim().length > 0) {
|
|
246
345
|
existingAnchors.add(rawAnchor.trim());
|
|
247
346
|
}
|
|
347
|
+
const rawAnchors = sufficiencyAudit?.anchors;
|
|
348
|
+
if (Array.isArray(rawAnchors)) {
|
|
349
|
+
for (const anchor of rawAnchors) {
|
|
350
|
+
if (typeof anchor !== "string" || anchor.trim().length === 0)
|
|
351
|
+
continue;
|
|
352
|
+
existingAnchors.add(anchor.trim());
|
|
353
|
+
}
|
|
354
|
+
}
|
|
248
355
|
}
|
|
249
356
|
return {
|
|
250
357
|
project,
|
|
@@ -285,7 +392,12 @@ export class TaskSufficiencyService {
|
|
|
285
392
|
const normalizedAnchor = normalizeAnchor("section", heading);
|
|
286
393
|
if (existingAnchors.has(normalizedAnchor))
|
|
287
394
|
continue;
|
|
288
|
-
items.push({
|
|
395
|
+
items.push({
|
|
396
|
+
kind: "section",
|
|
397
|
+
value: heading,
|
|
398
|
+
normalizedAnchor,
|
|
399
|
+
domain: deriveSectionDomain(heading),
|
|
400
|
+
});
|
|
289
401
|
if (items.length >= limit)
|
|
290
402
|
return items;
|
|
291
403
|
}
|
|
@@ -293,36 +405,82 @@ export class TaskSufficiencyService {
|
|
|
293
405
|
const normalizedAnchor = normalizeAnchor("folder", entry);
|
|
294
406
|
if (existingAnchors.has(normalizedAnchor))
|
|
295
407
|
continue;
|
|
296
|
-
items.push({
|
|
408
|
+
items.push({
|
|
409
|
+
kind: "folder",
|
|
410
|
+
value: entry,
|
|
411
|
+
normalizedAnchor,
|
|
412
|
+
domain: deriveFolderDomain(entry),
|
|
413
|
+
});
|
|
297
414
|
if (items.length >= limit)
|
|
298
415
|
return items;
|
|
299
416
|
}
|
|
300
417
|
return items;
|
|
301
418
|
}
|
|
419
|
+
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
|
+
const bundles = [];
|
|
431
|
+
for (const key of orderedKeys) {
|
|
432
|
+
const group = groups.get(key) ?? [];
|
|
433
|
+
for (let index = 0; index < group.length; index += GAP_BUNDLE_SIZE) {
|
|
434
|
+
if (bundles.length >= limit)
|
|
435
|
+
return bundles;
|
|
436
|
+
const chunk = group.slice(index, index + GAP_BUNDLE_SIZE);
|
|
437
|
+
const kinds = new Set(chunk.map((item) => item.kind));
|
|
438
|
+
bundles.push({
|
|
439
|
+
kind: kinds.size > 1 ? "mixed" : chunk[0]?.kind ?? "section",
|
|
440
|
+
domain: chunk[0]?.domain ?? "coverage",
|
|
441
|
+
values: chunk.map((item) => item.value),
|
|
442
|
+
normalizedAnchors: chunk.map((item) => item.normalizedAnchor),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return bundles;
|
|
447
|
+
}
|
|
302
448
|
async ensureTargetStory(project) {
|
|
303
449
|
const db = this.workspaceRepo.getDb();
|
|
304
|
-
const
|
|
450
|
+
const existingStories = await db.all(`SELECT
|
|
451
|
+
us.id AS story_id,
|
|
452
|
+
us.key AS story_key,
|
|
453
|
+
us.metadata_json AS story_metadata_json,
|
|
454
|
+
us.epic_id AS epic_id,
|
|
455
|
+
e.key AS epic_key,
|
|
456
|
+
e.metadata_json AS epic_metadata_json
|
|
305
457
|
FROM user_stories us
|
|
306
458
|
JOIN epics e ON e.id = us.epic_id
|
|
307
459
|
WHERE us.project_id = ?
|
|
308
|
-
ORDER BY COALESCE(us.priority, 2147483647), datetime(us.created_at), us.key
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
460
|
+
ORDER BY COALESCE(us.priority, 2147483647), datetime(us.created_at), us.key`, project.id);
|
|
461
|
+
for (const row of existingStories) {
|
|
462
|
+
const storyMetadata = readJsonSafe(row.story_metadata_json, null) ?? {};
|
|
463
|
+
const epicMetadata = readJsonSafe(row.epic_metadata_json, null) ?? {};
|
|
464
|
+
const storySource = typeof storyMetadata.source === "string" ? storyMetadata.source : undefined;
|
|
465
|
+
const epicSource = typeof epicMetadata.source === "string" ? epicMetadata.source : undefined;
|
|
466
|
+
if (storySource === "task-sufficiency-audit" || epicSource === "task-sufficiency-audit") {
|
|
467
|
+
return {
|
|
468
|
+
epicId: row.epic_id,
|
|
469
|
+
epicKey: row.epic_key,
|
|
470
|
+
storyId: row.story_id,
|
|
471
|
+
storyKey: row.story_key,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
317
474
|
}
|
|
318
475
|
let epicId = "";
|
|
319
476
|
let epicKey = "";
|
|
320
|
-
const existingEpic = await db.get(`SELECT id, key
|
|
477
|
+
const existingEpic = await db.get(`SELECT id, key, metadata_json
|
|
321
478
|
FROM epics
|
|
322
479
|
WHERE project_id = ?
|
|
323
|
-
ORDER BY COALESCE(priority, 2147483647), datetime(created_at), key
|
|
324
|
-
|
|
325
|
-
|
|
480
|
+
ORDER BY COALESCE(priority, 2147483647), datetime(created_at), key`, project.id);
|
|
481
|
+
const existingEpicMetadata = readJsonSafe(existingEpic?.metadata_json, null) ?? {};
|
|
482
|
+
const existingEpicSource = typeof existingEpicMetadata.source === "string" ? existingEpicMetadata.source : undefined;
|
|
483
|
+
if (existingEpic && existingEpicSource === "task-sufficiency-audit") {
|
|
326
484
|
epicId = existingEpic.id;
|
|
327
485
|
epicKey = existingEpic.key;
|
|
328
486
|
}
|
|
@@ -371,32 +529,45 @@ export class TaskSufficiencyService {
|
|
|
371
529
|
const existingTaskKeys = await this.workspaceRepo.listTaskKeys(params.storyId);
|
|
372
530
|
const taskKeyGen = createTaskKeyGenerator(params.storyKey, existingTaskKeys);
|
|
373
531
|
const now = new Date().toISOString();
|
|
374
|
-
const taskInserts = params.
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
532
|
+
const taskInserts = params.gapBundles.map((bundle, index) => {
|
|
533
|
+
const domainLabel = bundle.domain.replace(/[-_]+/g, " ").trim();
|
|
534
|
+
const titlePrefix = bundle.kind === "section"
|
|
535
|
+
? "Close SDS section coverage"
|
|
536
|
+
: bundle.kind === "folder"
|
|
537
|
+
? "Materialize SDS structure coverage"
|
|
538
|
+
: "Close SDS coverage bundle";
|
|
539
|
+
const title = `${titlePrefix}: ${domainLabel || "implementation scope"}`.slice(0, 180);
|
|
540
|
+
const objective = bundle.kind === "folder"
|
|
541
|
+
? `Create or update implementation artifacts for ${bundle.values.length} SDS folder-tree requirement(s).`
|
|
542
|
+
: `Implement missing functionality for ${bundle.values.length} SDS section requirement(s).`;
|
|
543
|
+
const scopeLines = bundle.values.map((value) => `- ${value}`);
|
|
544
|
+
const anchorLines = bundle.normalizedAnchors.map((anchor) => `- ${anchor}`);
|
|
380
545
|
const description = [
|
|
381
546
|
`## Objective`,
|
|
382
547
|
objective,
|
|
383
548
|
``,
|
|
384
549
|
`## Context`,
|
|
385
550
|
`- Generated by task-sufficiency-audit iteration ${params.iteration}.`,
|
|
386
|
-
`-
|
|
551
|
+
`- Coverage domain: ${bundle.domain}`,
|
|
552
|
+
``,
|
|
553
|
+
`## Anchor Scope`,
|
|
554
|
+
...scopeLines,
|
|
555
|
+
``,
|
|
556
|
+
`## Anchor Keys`,
|
|
557
|
+
...anchorLines,
|
|
387
558
|
``,
|
|
388
559
|
`## Implementation Plan`,
|
|
389
|
-
`-
|
|
390
|
-
`-
|
|
391
|
-
`-
|
|
560
|
+
`- Implement production code for this bundle before adding follow-up docs-only changes.`,
|
|
561
|
+
`- Update module wiring/contracts touched by these anchors.`,
|
|
562
|
+
`- Ensure each anchor has deterministic evidence (tests or checks).`,
|
|
392
563
|
``,
|
|
393
564
|
`## Testing`,
|
|
394
|
-
`- Add or update
|
|
395
|
-
`-
|
|
565
|
+
`- Add or update tests that validate each listed anchor scope.`,
|
|
566
|
+
`- Keep regression suites green after applying this bundle.`,
|
|
396
567
|
``,
|
|
397
568
|
`## Definition of Done`,
|
|
398
|
-
`-
|
|
399
|
-
`-
|
|
569
|
+
`- All anchor scope items in this bundle are represented in implementation code.`,
|
|
570
|
+
`- Validation evidence exists for every anchor key listed above.`,
|
|
400
571
|
].join("\n");
|
|
401
572
|
return {
|
|
402
573
|
projectId: params.project.id,
|
|
@@ -407,14 +578,16 @@ export class TaskSufficiencyService {
|
|
|
407
578
|
description,
|
|
408
579
|
type: "feature",
|
|
409
580
|
status: "not_started",
|
|
410
|
-
storyPoints:
|
|
581
|
+
storyPoints: Math.min(5, Math.max(2, bundle.normalizedAnchors.length)),
|
|
411
582
|
priority: params.maxPriority + index + 1,
|
|
412
583
|
metadata: {
|
|
413
584
|
sufficiencyAudit: {
|
|
414
585
|
source: "task-sufficiency-audit",
|
|
415
|
-
kind:
|
|
416
|
-
|
|
417
|
-
|
|
586
|
+
kind: bundle.kind,
|
|
587
|
+
domain: bundle.domain,
|
|
588
|
+
values: bundle.values,
|
|
589
|
+
anchor: bundle.normalizedAnchors[0],
|
|
590
|
+
anchors: bundle.normalizedAnchors,
|
|
418
591
|
iteration: params.iteration,
|
|
419
592
|
generatedAt: now,
|
|
420
593
|
},
|
|
@@ -482,8 +655,24 @@ export class TaskSufficiencyService {
|
|
|
482
655
|
if (sdsDocs.length === 0) {
|
|
483
656
|
throw new Error("task-sufficiency-audit requires an SDS document but none was found. Add docs/sds.md (or a fuzzy-match SDS doc) and retry.");
|
|
484
657
|
}
|
|
485
|
-
const
|
|
486
|
-
const
|
|
658
|
+
const warnings = [];
|
|
659
|
+
const rawSectionHeadings = unique(sdsDocs.flatMap((doc) => extractMarkdownHeadings(doc.content, SDS_HEADING_LIMIT))).slice(0, SDS_HEADING_LIMIT);
|
|
660
|
+
const rawFolderEntries = unique(sdsDocs.flatMap((doc) => extractFolderEntries(doc.content, SDS_FOLDER_LIMIT))).slice(0, SDS_FOLDER_LIMIT);
|
|
661
|
+
const sectionHeadings = unique(rawSectionHeadings
|
|
662
|
+
.map((heading) => normalizeHeadingCandidate(heading))
|
|
663
|
+
.filter((heading) => headingLooksImplementationRelevant(heading))).slice(0, SDS_HEADING_LIMIT);
|
|
664
|
+
const folderEntries = unique(rawFolderEntries
|
|
665
|
+
.map((entry) => normalizeFolderEntry(entry))
|
|
666
|
+
.filter((entry) => Boolean(entry))
|
|
667
|
+
.filter((entry) => folderEntryLooksRepoRelevant(entry))).slice(0, SDS_FOLDER_LIMIT);
|
|
668
|
+
const skippedHeadingSignals = Math.max(0, rawSectionHeadings.length - sectionHeadings.length);
|
|
669
|
+
const skippedFolderSignals = Math.max(0, rawFolderEntries.length - folderEntries.length);
|
|
670
|
+
if (skippedHeadingSignals > 0 || skippedFolderSignals > 0) {
|
|
671
|
+
warnings.push(`Filtered non-actionable SDS signals (headings=${skippedHeadingSignals}, folders=${skippedFolderSignals}) before remediation.`);
|
|
672
|
+
}
|
|
673
|
+
if (sectionHeadings.length === 0 && folderEntries.length === 0) {
|
|
674
|
+
warnings.push("No actionable implementation signals detected from SDS headings/folder tree after filtering; audit will report coverage only.");
|
|
675
|
+
}
|
|
487
676
|
await this.jobService.writeCheckpoint(job.id, {
|
|
488
677
|
stage: "sds_loaded",
|
|
489
678
|
timestamp: new Date().toISOString(),
|
|
@@ -491,10 +680,13 @@ export class TaskSufficiencyService {
|
|
|
491
680
|
docCount: sdsDocs.length,
|
|
492
681
|
headingSignals: sectionHeadings.length,
|
|
493
682
|
folderSignals: folderEntries.length,
|
|
683
|
+
rawHeadingSignals: rawSectionHeadings.length,
|
|
684
|
+
rawFolderSignals: rawFolderEntries.length,
|
|
685
|
+
filteredHeadingSignals: skippedHeadingSignals,
|
|
686
|
+
filteredFolderSignals: skippedFolderSignals,
|
|
494
687
|
docs: sdsDocs.map((doc) => path.relative(request.workspace.workspaceRoot, doc.path)),
|
|
495
688
|
},
|
|
496
689
|
});
|
|
497
|
-
const warnings = [];
|
|
498
690
|
const iterations = [];
|
|
499
691
|
let totalTasksAdded = 0;
|
|
500
692
|
const totalTasksUpdated = 0;
|
|
@@ -528,8 +720,9 @@ export class TaskSufficiencyService {
|
|
|
528
720
|
});
|
|
529
721
|
break;
|
|
530
722
|
}
|
|
531
|
-
const gapItems = this.buildGapItems(coverage, snapshot.existingAnchors, maxTasksPerIteration);
|
|
532
|
-
|
|
723
|
+
const gapItems = this.buildGapItems(coverage, snapshot.existingAnchors, maxTasksPerIteration * GAP_BUNDLE_SIZE);
|
|
724
|
+
const gapBundles = this.bundleGapItems(gapItems, maxTasksPerIteration);
|
|
725
|
+
if (gapBundles.length === 0) {
|
|
533
726
|
warnings.push(`Iteration ${iteration}: unresolved SDS gaps remain but no insertable gap items were identified.`);
|
|
534
727
|
iterations.push({
|
|
535
728
|
iteration,
|
|
@@ -560,7 +753,11 @@ export class TaskSufficiencyService {
|
|
|
560
753
|
missingSectionCount: coverage.missingSectionHeadings.length,
|
|
561
754
|
missingFolderCount: coverage.missingFolderEntries.length,
|
|
562
755
|
action: "dry_run",
|
|
563
|
-
proposedGapItems:
|
|
756
|
+
proposedGapItems: gapBundles.map((bundle) => ({
|
|
757
|
+
kind: bundle.kind,
|
|
758
|
+
domain: bundle.domain,
|
|
759
|
+
values: bundle.values,
|
|
760
|
+
})),
|
|
564
761
|
},
|
|
565
762
|
});
|
|
566
763
|
break;
|
|
@@ -572,7 +769,7 @@ export class TaskSufficiencyService {
|
|
|
572
769
|
storyKey: target.storyKey,
|
|
573
770
|
epicId: target.epicId,
|
|
574
771
|
maxPriority: snapshot.maxPriority,
|
|
575
|
-
|
|
772
|
+
gapBundles,
|
|
576
773
|
iteration,
|
|
577
774
|
jobId: job.id,
|
|
578
775
|
commandRunId: commandRun.id,
|
|
@@ -600,7 +797,7 @@ export class TaskSufficiencyService {
|
|
|
600
797
|
addedCount: createdTaskKeys.length,
|
|
601
798
|
},
|
|
602
799
|
});
|
|
603
|
-
await this.jobService.appendLog(job.id, `Iteration ${iteration}: added ${createdTaskKeys.length} task(s): ${createdTaskKeys.join(", ")}\n`);
|
|
800
|
+
await this.jobService.appendLog(job.id, `Iteration ${iteration}: added ${createdTaskKeys.length} remediation task(s) from ${gapBundles.length} gap bundle(s): ${createdTaskKeys.join(", ")}\n`);
|
|
604
801
|
}
|
|
605
802
|
const finalSnapshot = await this.loadProjectSnapshot(request.projectKey);
|
|
606
803
|
const finalCoverage = this.evaluateCoverage(finalSnapshot.corpus, sectionHeadings, folderEntries, finalSnapshot.existingAnchors);
|
|
@@ -624,8 +821,15 @@ export class TaskSufficiencyService {
|
|
|
624
821
|
totalTasksUpdated,
|
|
625
822
|
docs: sdsDocs.map((doc) => ({
|
|
626
823
|
path: path.relative(request.workspace.workspaceRoot, doc.path),
|
|
627
|
-
headingSignals: extractMarkdownHeadings(doc.content, SDS_HEADING_LIMIT)
|
|
628
|
-
|
|
824
|
+
headingSignals: extractMarkdownHeadings(doc.content, SDS_HEADING_LIMIT)
|
|
825
|
+
.map((heading) => normalizeHeadingCandidate(heading))
|
|
826
|
+
.filter((heading) => headingLooksImplementationRelevant(heading)).length,
|
|
827
|
+
folderSignals: extractFolderEntries(doc.content, SDS_FOLDER_LIMIT)
|
|
828
|
+
.map((entry) => normalizeFolderEntry(entry))
|
|
829
|
+
.filter((entry) => Boolean(entry))
|
|
830
|
+
.filter((entry) => folderEntryLooksRepoRelevant(entry)).length,
|
|
831
|
+
rawHeadingSignals: extractMarkdownHeadings(doc.content, SDS_HEADING_LIMIT).length,
|
|
832
|
+
rawFolderSignals: extractFolderEntries(doc.content, SDS_FOLDER_LIMIT).length,
|
|
629
833
|
})),
|
|
630
834
|
finalCoverage: {
|
|
631
835
|
coverageRatio: finalCoverage.coverageRatio,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcoda/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
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/generators": "0.1.
|
|
38
|
-
"@mcoda/integrations": "0.1.
|
|
39
|
-
"@mcoda/
|
|
35
|
+
"@mcoda/shared": "0.1.27",
|
|
36
|
+
"@mcoda/db": "0.1.27",
|
|
37
|
+
"@mcoda/generators": "0.1.27",
|
|
38
|
+
"@mcoda/integrations": "0.1.27",
|
|
39
|
+
"@mcoda/agents": "0.1.27"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.json",
|