@mcoda/core 0.1.23 → 0.1.26

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.
@@ -194,6 +194,22 @@ const formatTaskSummary = (task) => {
194
194
  .filter(Boolean)
195
195
  .join("\n");
196
196
  };
197
+ const splitChildReferenceFields = ["taskKey", "key", "localId", "id", "slug", "alias", "ref"];
198
+ const normalizeSplitDependencyRef = (value) => value.trim().toLowerCase();
199
+ const collectSplitChildReferences = (child) => {
200
+ const references = [];
201
+ const childRecord = child;
202
+ for (const field of splitChildReferenceFields) {
203
+ const candidate = childRecord[field];
204
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
205
+ references.push(candidate.trim());
206
+ }
207
+ }
208
+ if (typeof child.title === "string" && child.title.trim().length > 0) {
209
+ references.push(child.title.trim());
210
+ }
211
+ return Array.from(new Set(references));
212
+ };
197
213
  export class RefineTasksService {
198
214
  constructor(workspace, deps) {
199
215
  this.workspace = workspace;
@@ -880,10 +896,39 @@ export class RefineTasksService {
880
896
  }
881
897
  if (op.op === "split_task") {
882
898
  const split = op;
883
- const invalidDep = split.children.some((child) => child.dependsOn?.some((dep) => !keySet.has(dep)));
899
+ const taskKeysByNormalized = new Map();
900
+ for (const key of keySet) {
901
+ taskKeysByNormalized.set(normalizeSplitDependencyRef(key), key);
902
+ }
903
+ const siblingReferences = new Set();
904
+ const selfReferencesByChild = split.children.map((child) => {
905
+ const selfReferences = new Set();
906
+ for (const reference of collectSplitChildReferences(child)) {
907
+ const normalized = normalizeSplitDependencyRef(reference);
908
+ if (!normalized)
909
+ continue;
910
+ selfReferences.add(normalized);
911
+ siblingReferences.add(normalized);
912
+ }
913
+ return selfReferences;
914
+ });
915
+ const invalidDep = split.children.some((child) => child.dependsOn?.some((dep) => {
916
+ const normalized = normalizeSplitDependencyRef(dep);
917
+ if (!normalized)
918
+ return false;
919
+ if (taskKeysByNormalized.has(normalized))
920
+ return false;
921
+ if (siblingReferences.has(normalized))
922
+ return false;
923
+ return true;
924
+ }));
884
925
  if (invalidDep) {
885
926
  return { valid: false, reason: "Split child references unknown dependency" };
886
927
  }
928
+ const selfDependency = split.children.some((child, index) => child.dependsOn?.some((dep) => selfReferencesByChild[index]?.has(normalizeSplitDependencyRef(dep))));
929
+ if (selfDependency) {
930
+ return { valid: false, reason: "Split child cannot depend on itself" };
931
+ }
887
932
  if (split.children.some((child) => child.storyPoints !== undefined && child.storyPoints !== null && (child.storyPoints < 0 || child.storyPoints > 13))) {
888
933
  return { valid: false, reason: "Child story points out of bounds" };
889
934
  }
@@ -1049,8 +1094,25 @@ export class RefineTasksService {
1049
1094
  createdAt: new Date().toISOString(),
1050
1095
  });
1051
1096
  }
1052
- for (const child of op.children) {
1053
- const childKey = keyGen();
1097
+ const existingTaskKeyByNormalized = new Map();
1098
+ for (const key of taskByKey.keys()) {
1099
+ existingTaskKeyByNormalized.set(normalizeSplitDependencyRef(key), key);
1100
+ }
1101
+ const childKeys = op.children.map(() => keyGen());
1102
+ const childRefToKey = new Map();
1103
+ op.children.forEach((child, index) => {
1104
+ const childKey = childKeys[index];
1105
+ childRefToKey.set(normalizeSplitDependencyRef(childKey), childKey);
1106
+ for (const reference of collectSplitChildReferences(child)) {
1107
+ const normalized = normalizeSplitDependencyRef(reference);
1108
+ if (!normalized || childRefToKey.has(normalized))
1109
+ continue;
1110
+ childRefToKey.set(normalized, childKey);
1111
+ }
1112
+ });
1113
+ for (let index = 0; index < op.children.length; index += 1) {
1114
+ const child = op.children[index];
1115
+ const childKey = childKeys[index];
1054
1116
  const childSp = child.storyPoints ?? null;
1055
1117
  if (childSp) {
1056
1118
  storyPointsDelta += childSp;
@@ -1082,17 +1144,24 @@ export class RefineTasksService {
1082
1144
  openapiVersionAtCreation: target.openapiVersionAtCreation ?? null,
1083
1145
  };
1084
1146
  newTasks.push(childInsert);
1085
- const dependsOn = child.dependsOn ?? [];
1086
- for (const depKey of dependsOn) {
1087
- const depTask = taskByKey.get(depKey);
1088
- if (depTask) {
1089
- pendingDeps.push({
1090
- childKey,
1091
- dependsOnId: depTask.id,
1092
- dependsOnKey: depTask.key,
1093
- relationType: "blocks",
1094
- });
1147
+ for (const dependencyReference of child.dependsOn ?? []) {
1148
+ const normalizedReference = normalizeSplitDependencyRef(dependencyReference);
1149
+ if (!normalizedReference)
1150
+ continue;
1151
+ const dependencyKey = existingTaskKeyByNormalized.get(normalizedReference) ?? childRefToKey.get(normalizedReference);
1152
+ if (!dependencyKey) {
1153
+ warnings.push(`Skipped split dependency ${childKey}->${dependencyReference}: unresolved sibling or task reference.`);
1154
+ continue;
1155
+ }
1156
+ if (dependencyKey === childKey) {
1157
+ warnings.push(`Skipped split dependency ${childKey}->${dependencyReference}: self dependency.`);
1158
+ continue;
1095
1159
  }
1160
+ pendingDeps.push({
1161
+ childKey,
1162
+ dependsOnKey: dependencyKey,
1163
+ relationType: "blocks",
1164
+ });
1096
1165
  }
1097
1166
  taskByKey.set(childKey, {
1098
1167
  ...childInsert,
@@ -1247,9 +1316,14 @@ export class RefineTasksService {
1247
1316
  }
1248
1317
  for (const dep of allowedDeps) {
1249
1318
  const childId = idByKey.get(dep.childKey);
1250
- if (childId) {
1251
- deps.push({ taskId: childId, dependsOnTaskId: dep.dependsOnId, relationType: dep.relationType });
1319
+ if (!childId)
1320
+ continue;
1321
+ const dependsOnId = idByKey.get(dep.dependsOnKey) ?? taskByKey.get(dep.dependsOnKey)?.id;
1322
+ if (!dependsOnId) {
1323
+ warnings.push(`Skipped refine dependency ${dep.childKey}->${dep.dependsOnKey}: dependency task not found.`);
1324
+ continue;
1252
1325
  }
1326
+ deps.push({ taskId: childId, dependsOnTaskId: dependsOnId, relationType: dep.relationType });
1253
1327
  }
1254
1328
  if (deps.length > 0) {
1255
1329
  stage = "insert:deps";
@@ -0,0 +1,73 @@
1
+ import { WorkspaceRepository } from "@mcoda/db";
2
+ import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
3
+ import { JobService } from "../jobs/JobService.js";
4
+ export interface TaskSufficiencyAuditRequest {
5
+ workspace: WorkspaceResolution;
6
+ projectKey: string;
7
+ dryRun?: boolean;
8
+ maxIterations?: number;
9
+ maxTasksPerIteration?: number;
10
+ minCoverageRatio?: number;
11
+ sourceCommand?: string;
12
+ }
13
+ export interface TaskSufficiencyAuditIteration {
14
+ iteration: number;
15
+ coverageRatio: number;
16
+ totalSignals: number;
17
+ missingSectionCount: number;
18
+ missingFolderCount: number;
19
+ createdTaskKeys: string[];
20
+ }
21
+ export interface TaskSufficiencyAuditResult {
22
+ jobId: string;
23
+ commandRunId: string;
24
+ projectKey: string;
25
+ sourceCommand?: string;
26
+ satisfied: boolean;
27
+ dryRun: boolean;
28
+ totalTasksAdded: number;
29
+ totalTasksUpdated: number;
30
+ maxIterations: number;
31
+ minCoverageRatio: number;
32
+ finalCoverageRatio: number;
33
+ remainingSectionHeadings: string[];
34
+ remainingFolderEntries: string[];
35
+ remainingGaps: {
36
+ sections: number;
37
+ folders: number;
38
+ total: number;
39
+ };
40
+ iterations: TaskSufficiencyAuditIteration[];
41
+ reportPath: string;
42
+ reportHistoryPath?: string;
43
+ warnings: string[];
44
+ }
45
+ type TaskSufficiencyDeps = {
46
+ workspaceRepo: WorkspaceRepository;
47
+ jobService: JobService;
48
+ };
49
+ export declare class TaskSufficiencyService {
50
+ private readonly workspaceRepo;
51
+ private readonly jobService;
52
+ private readonly ownsWorkspaceRepo;
53
+ private readonly ownsJobService;
54
+ private readonly workspace;
55
+ constructor(workspace: WorkspaceResolution, deps: TaskSufficiencyDeps, ownership?: {
56
+ ownsWorkspaceRepo?: boolean;
57
+ ownsJobService?: boolean;
58
+ });
59
+ static create(workspace: WorkspaceResolution): Promise<TaskSufficiencyService>;
60
+ close(): Promise<void>;
61
+ private discoverSdsPaths;
62
+ private walkSdsCandidates;
63
+ private loadSdsSources;
64
+ private loadProjectSnapshot;
65
+ private evaluateCoverage;
66
+ private buildGapItems;
67
+ private ensureTargetStory;
68
+ private insertGapTasks;
69
+ private writeReportArtifacts;
70
+ runAudit(request: TaskSufficiencyAuditRequest): Promise<TaskSufficiencyAuditResult>;
71
+ }
72
+ export {};
73
+ //# sourceMappingURL=TaskSufficiencyService.d.ts.map
@@ -0,0 +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"}