@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.
@@ -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":"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"}
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
- ensureNonEmpty(description, "Summarize the problem, users, and constraints for this epic."),
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, "Provide 5–10 testable acceptance criteria."),
425
+ formatBullets(acceptance, "Define measurable and testable outcomes for this epic."),
424
426
  "* **Related Documentation / References**",
425
- formatBullets(relatedDocs, "Link relevant docdex entries and sections."),
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
- ensureNonEmpty(userStory, `As a user, I want ${title} so that it delivers value.`),
437
+ userStoryText,
434
438
  "* **Context**",
435
439
  "",
436
- ensureNonEmpty(description, "Context for systems, dependencies, and scope."),
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 = ensureNonEmpty(description, `Deliver ${title} for story ${storyKey}.`);
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
- `- Implement ${title} with file/module-level changes aligned to the objective.`,
477
+ `Implement ${title} with concrete file/module-level changes aligned to the objective.`,
486
478
  dependencies.length
487
- ? `- Respect dependency order before completion: ${dependencies.join(", ")}.`
488
- : "- Validate assumptions and finalize concrete implementation steps before coding.",
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
- ? [`- Delivery depends on upstream tasks: ${dependencies.join(", ")}.`]
492
- : ["- Keep implementation aligned to SDS/OpenAPI contracts to avoid drift."];
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, "Docdex excerpts, SDS/PDR/RFP sections, OpenAPI endpoints."),
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, "Enumerate prerequisite tasks by key."),
514
+ formatBullets(dependencies, "None."),
523
515
  "* **Risks & Gotchas**",
524
516
  formatBullets(riskLines, defaultRisks.join(" ")),
525
517
  "* **Related Documentation / References**",
526
- formatBullets(relatedDocs, "Docdex handles or file paths to consult."),
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 creating EPICS ONLY for project ${projectKey}.`,
2191
- "Follow mcoda SDS epic template:",
2192
- "- Context/Problem; Goals & Outcomes; In Scope; Out of Scope; Key Flows; Non-functional Requirements; Dependencies & Constraints; Risks & Open Questions; Acceptance Criteria; Related Documentation.",
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
- "- Prefer dependency-first sequencing: foundational codebase/service setup epics should precede dependent feature epics.",
2200
- "- Keep output technology-agnostic and derived from docs; do not assume specific stacks unless docs state them.",
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
- "Use the User Story template: User Story; Context; Preconditions; Main Flow; Alternative/Error Flows; UX/UI; Data & Integrations; Acceptance Criteria; NFR; Related Docs.",
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
- "Use the Task template: Objective; Context; Inputs; Implementation Plan; DoD; Testing & QA; Dependencies; Risks; References.",
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(),
@@ -64,6 +64,7 @@ export declare class TaskSufficiencyService {
64
64
  private loadProjectSnapshot;
65
65
  private evaluateCoverage;
66
66
  private buildGapItems;
67
+ private bundleGapItems;
67
68
  private ensureTargetStory;
68
69
  private insertGapTasks;
69
70
  private writeReportArtifacts;
@@ -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;AAsCnD,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;AAqFF,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;IAgEjC,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,aAAa;YAqBP,iBAAiB;YAyFjB,cAAc;YAkGd,oBAAoB;IAkB5B,QAAQ,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC;CA4R1F"}
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 rawAnchor = metadata?.sufficiencyAudit?.anchor;
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({ kind: "section", value: heading, normalizedAnchor });
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({ kind: "folder", value: entry, normalizedAnchor });
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 existingStory = await db.get(`SELECT us.id AS story_id, us.key AS story_key, us.epic_id AS epic_id, e.key AS epic_key
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
- LIMIT 1`, project.id);
310
- if (existingStory) {
311
- return {
312
- epicId: existingStory.epic_id,
313
- epicKey: existingStory.epic_key,
314
- storyId: existingStory.story_id,
315
- storyKey: existingStory.story_key,
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
- LIMIT 1`, project.id);
325
- if (existingEpic) {
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.gapItems.map((gap, index) => {
375
- const titlePrefix = gap.kind === "section" ? "Cover SDS section" : "Materialize SDS folder entry";
376
- const title = `${titlePrefix}: ${gap.value}`.slice(0, 180);
377
- const objective = gap.kind === "section"
378
- ? `Implement or update product code to satisfy the SDS section \"${gap.value}\".`
379
- : `Create/update codebase artifacts required by SDS folder-tree entry \"${gap.value}\".`;
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
- `- Anchor: ${gap.normalizedAnchor}`,
551
+ `- Coverage domain: ${bundle.domain}`,
552
+ ``,
553
+ `## Anchor Scope`,
554
+ ...scopeLines,
555
+ ``,
556
+ `## Anchor Keys`,
557
+ ...anchorLines,
387
558
  ``,
388
559
  `## Implementation Plan`,
389
- `- Inspect SDS and current implementation for this anchor.`,
390
- `- Add or update production code and wiring to satisfy the requirement.`,
391
- `- Update impacted docs/contracts if the implementation surface changes.`,
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 unit/component/integration tests for this anchor.`,
395
- `- Ensure existing regression suites remain green.`,
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
- `- Anchor requirement is fully represented in code.`,
399
- `- Tests covering this scope pass.`,
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: 1,
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: gap.kind,
416
- value: gap.value,
417
- anchor: gap.normalizedAnchor,
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 sectionHeadings = unique(sdsDocs.flatMap((doc) => extractMarkdownHeadings(doc.content, SDS_HEADING_LIMIT))).slice(0, SDS_HEADING_LIMIT);
486
- const folderEntries = unique(sdsDocs.flatMap((doc) => extractFolderEntries(doc.content, SDS_FOLDER_LIMIT))).slice(0, SDS_FOLDER_LIMIT);
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
- if (gapItems.length === 0) {
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: gapItems.map((item) => ({ kind: item.kind, value: item.value })),
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
- gapItems,
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).length,
628
- folderSignals: extractFolderEntries(doc.content, SDS_FOLDER_LIMIT).length,
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.26",
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.26",
36
- "@mcoda/agents": "0.1.26",
37
- "@mcoda/generators": "0.1.26",
38
- "@mcoda/integrations": "0.1.26",
39
- "@mcoda/db": "0.1.26"
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",