@mcoda/core 0.1.18 → 0.1.19

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.
@@ -8,6 +8,8 @@ export declare class EstimateService {
8
8
  private computeElapsedLaneHours;
9
9
  private computeDurations;
10
10
  private computeEtas;
11
+ private buildStatusCounts;
12
+ private buildCompletion;
11
13
  estimate(options: Omit<EstimateOptions, "workspace">): Promise<EstimateResult>;
12
14
  close(): Promise<void>;
13
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EstimateService.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/EstimateService.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmC,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAI/E,qBAAa,eAAe;IACN,OAAO,CAAC,SAAS;IAArC,OAAO;WAEM,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAI7E,OAAO,CAAC,UAAU;YAMJ,uBAAuB;IAmCrC,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,WAAW;IA+Bb,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAwD9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
1
+ {"version":3,"file":"EstimateService.d.ts","sourceRoot":"","sources":["../../../src/services/estimate/EstimateService.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmC,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAI/E,qBAAa,eAAe;IACN,OAAO,CAAC,SAAS;IAArC,OAAO;WAEM,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAI7E,OAAO,CAAC,UAAU;YAMJ,uBAAuB;IAmCrC,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,WAAW;IA+BnB,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,eAAe;IA0BjB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IA4D9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -91,6 +91,56 @@ export class EstimateService {
91
91
  completeEta,
92
92
  };
93
93
  }
94
+ buildStatusCounts(backlogTasks) {
95
+ const counts = {
96
+ total: backlogTasks.length,
97
+ readyToCodeReview: 0,
98
+ failed: 0,
99
+ inProgress: 0,
100
+ readyToQa: 0,
101
+ completed: 0,
102
+ };
103
+ for (const task of backlogTasks) {
104
+ const status = task.status.trim().toLowerCase();
105
+ if (status === "ready_to_code_review")
106
+ counts.readyToCodeReview += 1;
107
+ if (status === "failed")
108
+ counts.failed += 1;
109
+ if (status === "in_progress")
110
+ counts.inProgress += 1;
111
+ if (status === "ready_to_qa")
112
+ counts.readyToQa += 1;
113
+ if (status === "completed")
114
+ counts.completed += 1;
115
+ }
116
+ return counts;
117
+ }
118
+ buildCompletion(statusCounts) {
119
+ const ratio = (done, total) => {
120
+ if (!total || total <= 0)
121
+ return 0;
122
+ return Math.max(0, Math.min(100, (done / total) * 100));
123
+ };
124
+ const workDone = statusCounts.readyToCodeReview + statusCounts.readyToQa + statusCounts.completed;
125
+ const qaDone = statusCounts.readyToQa + statusCounts.completed;
126
+ return {
127
+ workOnTasks: {
128
+ done: workDone,
129
+ total: statusCounts.total,
130
+ percent: ratio(workDone, statusCounts.total),
131
+ },
132
+ readyToQa: {
133
+ done: qaDone,
134
+ total: statusCounts.total,
135
+ percent: ratio(qaDone, statusCounts.total),
136
+ },
137
+ done: {
138
+ done: statusCounts.completed,
139
+ total: statusCounts.total,
140
+ percent: ratio(statusCounts.completed, statusCounts.total),
141
+ },
142
+ };
143
+ }
94
144
  async estimate(options) {
95
145
  const backlogService = await BacklogService.create(this.workspace);
96
146
  let backlogTotals;
@@ -131,6 +181,8 @@ export class EstimateService {
131
181
  const elapsedImplementationHours = await this.computeElapsedLaneHours(inProgressTaskIds, "in_progress");
132
182
  const durationsHours = this.computeDurations(backlogTotals, effectiveVelocity, elapsedImplementationHours);
133
183
  const etas = this.computeEtas(backlogTotals, effectiveVelocity, durationsHours);
184
+ const statusCounts = this.buildStatusCounts(backlogTasks);
185
+ const completion = this.buildCompletion(statusCounts);
134
186
  return {
135
187
  scope: {
136
188
  workspaceId: this.workspace.workspaceId,
@@ -143,6 +195,8 @@ export class EstimateService {
143
195
  effectiveVelocity,
144
196
  durationsHours,
145
197
  etas,
198
+ statusCounts,
199
+ completion,
146
200
  };
147
201
  }
148
202
  async close() {
@@ -1 +1 @@
1
- {"version":3,"file":"WorkOnTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/WorkOnTasksService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WorkOnTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/execution/WorkOnTasksService.ts"],"names":[],"mappings":"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"}
@@ -3405,7 +3405,9 @@ export class WorkOnTasksService {
3405
3405
  if (request.missingTestsPolicy && !requestedMissingTestsPolicy) {
3406
3406
  runnerWarnings.push(`Unknown missing-tests policy "${String(request.missingTestsPolicy)}"; using "${missingTestsPolicy}".`);
3407
3407
  }
3408
- const ignoreStatusFilter = Boolean(request.taskKeys?.length) || request.ignoreStatusFilter === true;
3408
+ const explicitTaskSelection = Boolean(request.taskKeys?.length);
3409
+ const ignoreDependencies = request.ignoreDependencies === true || explicitTaskSelection;
3410
+ const ignoreStatusFilter = explicitTaskSelection || request.ignoreStatusFilter === true;
3409
3411
  const { filtered: statusFilter, rejected } = ignoreStatusFilter
3410
3412
  ? { filtered: request.statusFilter ?? [], rejected: [] }
3411
3413
  : filterTaskStatuses(request.statusFilter, WORK_ALLOWED_STATUSES, WORK_ALLOWED_STATUSES);
@@ -3443,7 +3445,7 @@ export class WorkOnTasksService {
3443
3445
  ignoreStatusFilter,
3444
3446
  includeTypes,
3445
3447
  excludeTypes,
3446
- ignoreDependencies: true,
3448
+ ignoreDependencies,
3447
3449
  limit: request.limit,
3448
3450
  parallel: request.parallel,
3449
3451
  noCommit: request.noCommit ?? false,
@@ -3527,7 +3529,7 @@ export class WorkOnTasksService {
3527
3529
  ignoreStatusFilter,
3528
3530
  includeTypes,
3529
3531
  excludeTypes,
3530
- ignoreDependencies: true,
3532
+ ignoreDependencies,
3531
3533
  limit: request.limit,
3532
3534
  parallel: request.parallel,
3533
3535
  });
@@ -58,6 +58,10 @@ export declare class CreateTasksService {
58
58
  });
59
59
  static create(workspace: WorkspaceResolution): Promise<CreateTasksService>;
60
60
  close(): Promise<void>;
61
+ private storyScopeKey;
62
+ private taskScopeKey;
63
+ private scopeStory;
64
+ private scopeTask;
61
65
  private seedPriorities;
62
66
  private resolveAgent;
63
67
  private ensureRatingService;
@@ -81,6 +85,7 @@ export declare class CreateTasksService {
81
85
  private shouldInjectStructureBootstrap;
82
86
  private injectStructureBootstrapPlan;
83
87
  private enforceStoryScopedDependencies;
88
+ private validatePlanLocalIdentifiers;
84
89
  private buildQaPreflight;
85
90
  private buildQaOverrides;
86
91
  private buildDocContext;
@@ -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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"}
@@ -593,6 +593,18 @@ export class CreateTasksService {
593
593
  const docdex = this.docdex;
594
594
  await swallow(docdex?.close?.bind(docdex));
595
595
  }
596
+ storyScopeKey(epicLocalId, storyLocalId) {
597
+ return `${epicLocalId}::${storyLocalId}`;
598
+ }
599
+ taskScopeKey(epicLocalId, storyLocalId, taskLocalId) {
600
+ return `${epicLocalId}::${storyLocalId}::${taskLocalId}`;
601
+ }
602
+ scopeStory(story) {
603
+ return this.storyScopeKey(story.epicLocalId, story.localId);
604
+ }
605
+ scopeTask(task) {
606
+ return this.taskScopeKey(task.epicLocalId, task.storyLocalId, task.localId);
607
+ }
596
608
  async seedPriorities(projectKey) {
597
609
  const ordering = await this.taskOrderingFactory(this.workspace, { recordTelemetry: false });
598
610
  try {
@@ -1068,7 +1080,7 @@ export class CreateTasksService {
1068
1080
  const services = this.sortServicesByDependency(Array.from(aliases.keys()), dependencies);
1069
1081
  return { services, dependencies, aliases };
1070
1082
  }
1071
- orderStoryTasksByDependencies(storyTasks, serviceRank, taskServiceByLocalId) {
1083
+ orderStoryTasksByDependencies(storyTasks, serviceRank, taskServiceByScope) {
1072
1084
  const byLocalId = new Map(storyTasks.map((task) => [task.localId, task]));
1073
1085
  const indegree = new Map();
1074
1086
  const outgoing = new Map();
@@ -1090,8 +1102,8 @@ export class CreateTasksService {
1090
1102
  const classB = classifyTask({ title: b.title ?? "", description: b.description, type: b.type });
1091
1103
  if (classA.foundation !== classB.foundation)
1092
1104
  return classA.foundation ? -1 : 1;
1093
- const rankA = serviceRank.get(taskServiceByLocalId.get(a.localId) ?? "") ?? Number.MAX_SAFE_INTEGER;
1094
- const rankB = serviceRank.get(taskServiceByLocalId.get(b.localId) ?? "") ?? Number.MAX_SAFE_INTEGER;
1105
+ const rankA = serviceRank.get(taskServiceByScope.get(this.scopeTask(a)) ?? "") ?? Number.MAX_SAFE_INTEGER;
1106
+ const rankB = serviceRank.get(taskServiceByScope.get(this.scopeTask(b)) ?? "") ?? Number.MAX_SAFE_INTEGER;
1095
1107
  if (rankA !== rankB)
1096
1108
  return rankA - rankB;
1097
1109
  const priorityA = a.priorityHint ?? Number.MAX_SAFE_INTEGER;
@@ -1137,22 +1149,23 @@ export class CreateTasksService {
1137
1149
  const epics = plan.epics.map((epic) => ({ ...epic }));
1138
1150
  const stories = plan.stories.map((story) => ({ ...story }));
1139
1151
  const tasks = plan.tasks.map((task) => ({ ...task, dependsOnKeys: uniqueStrings(task.dependsOnKeys ?? []) }));
1140
- const storyByLocalId = new Map(stories.map((story) => [story.localId, story]));
1141
- const taskServiceByLocalId = new Map();
1152
+ const storyByScope = new Map(stories.map((story) => [this.scopeStory(story), story]));
1153
+ const taskServiceByScope = new Map();
1142
1154
  for (const task of tasks) {
1143
1155
  const text = `${task.title ?? ""}\n${task.description ?? ""}`;
1144
- taskServiceByLocalId.set(task.localId, resolveEntityService(text));
1156
+ taskServiceByScope.set(this.scopeTask(task), resolveEntityService(text));
1145
1157
  }
1146
1158
  const tasksByStory = new Map();
1147
1159
  for (const task of tasks) {
1148
- const bucket = tasksByStory.get(task.storyLocalId) ?? [];
1160
+ const storyScope = this.storyScopeKey(task.epicLocalId, task.storyLocalId);
1161
+ const bucket = tasksByStory.get(storyScope) ?? [];
1149
1162
  bucket.push(task);
1150
- tasksByStory.set(task.storyLocalId, bucket);
1163
+ tasksByStory.set(storyScope, bucket);
1151
1164
  }
1152
1165
  for (const storyTasks of tasksByStory.values()) {
1153
1166
  const tasksByService = new Map();
1154
1167
  for (const task of storyTasks) {
1155
- const service = taskServiceByLocalId.get(task.localId);
1168
+ const service = taskServiceByScope.get(this.scopeTask(task));
1156
1169
  if (!service)
1157
1170
  continue;
1158
1171
  const serviceTasks = tasksByService.get(service) ?? [];
@@ -1163,7 +1176,7 @@ export class CreateTasksService {
1163
1176
  serviceTasks.sort((a, b) => (a.priorityHint ?? Number.MAX_SAFE_INTEGER) - (b.priorityHint ?? Number.MAX_SAFE_INTEGER));
1164
1177
  }
1165
1178
  for (const task of storyTasks) {
1166
- const service = taskServiceByLocalId.get(task.localId);
1179
+ const service = taskServiceByScope.get(this.scopeTask(task));
1167
1180
  if (!service)
1168
1181
  continue;
1169
1182
  const requiredServices = graph.dependencies.get(service);
@@ -1179,21 +1192,22 @@ export class CreateTasksService {
1179
1192
  }
1180
1193
  }
1181
1194
  }
1182
- const storyRankByLocalId = new Map();
1195
+ const storyRankByScope = new Map();
1183
1196
  for (const story of stories) {
1184
- const storyTasks = tasksByStory.get(story.localId) ?? [];
1197
+ const storyScope = this.scopeStory(story);
1198
+ const storyTasks = tasksByStory.get(storyScope) ?? [];
1185
1199
  const taskRanks = storyTasks
1186
- .map((task) => serviceRank.get(taskServiceByLocalId.get(task.localId) ?? ""))
1200
+ .map((task) => serviceRank.get(taskServiceByScope.get(this.scopeTask(task)) ?? ""))
1187
1201
  .filter((value) => typeof value === "number");
1188
1202
  const storyTextRank = serviceRank.get(resolveEntityService(`${story.title}\n${story.description ?? ""}\n${story.userStory ?? ""}`) ?? "");
1189
1203
  const rank = taskRanks.length > 0 ? Math.min(...taskRanks) : storyTextRank ?? Number.MAX_SAFE_INTEGER;
1190
- storyRankByLocalId.set(story.localId, rank);
1204
+ storyRankByScope.set(storyScope, rank);
1191
1205
  }
1192
1206
  const epicRankByLocalId = new Map();
1193
1207
  for (const epic of epics) {
1194
1208
  const epicStories = stories.filter((story) => story.epicLocalId === epic.localId);
1195
1209
  const storyRanks = epicStories
1196
- .map((story) => storyRankByLocalId.get(story.localId))
1210
+ .map((story) => storyRankByScope.get(this.scopeStory(story)))
1197
1211
  .filter((value) => typeof value === "number");
1198
1212
  const epicTextRank = serviceRank.get(resolveEntityService(`${epic.title}\n${epic.description ?? ""}`) ?? "");
1199
1213
  const rank = storyRanks.length > 0 ? Math.min(...storyRanks) : epicTextRank ?? Number.MAX_SAFE_INTEGER;
@@ -1228,8 +1242,8 @@ export class CreateTasksService {
1228
1242
  const bootstrapB = isBootstrap(`${b.title} ${b.description ?? ""}`);
1229
1243
  if (bootstrapA !== bootstrapB)
1230
1244
  return bootstrapA ? -1 : 1;
1231
- const rankA = storyRankByLocalId.get(a.localId) ?? Number.MAX_SAFE_INTEGER;
1232
- const rankB = storyRankByLocalId.get(b.localId) ?? Number.MAX_SAFE_INTEGER;
1245
+ const rankA = storyRankByScope.get(this.scopeStory(a)) ?? Number.MAX_SAFE_INTEGER;
1246
+ const rankB = storyRankByScope.get(this.scopeStory(b)) ?? Number.MAX_SAFE_INTEGER;
1233
1247
  if (rankA !== rankB)
1234
1248
  return rankA - rankB;
1235
1249
  const priorityA = a.priorityHint ?? Number.MAX_SAFE_INTEGER;
@@ -1241,31 +1255,31 @@ export class CreateTasksService {
1241
1255
  epicStories.forEach((story, index) => {
1242
1256
  story.priorityHint = index + 1;
1243
1257
  storiesOrdered.push(story);
1244
- const storyTasks = tasksByStory.get(story.localId) ?? [];
1245
- const orderedTasks = this.orderStoryTasksByDependencies(storyTasks, serviceRank, taskServiceByLocalId);
1258
+ const storyTasks = tasksByStory.get(this.scopeStory(story)) ?? [];
1259
+ const orderedTasks = this.orderStoryTasksByDependencies(storyTasks, serviceRank, taskServiceByScope);
1246
1260
  orderedTasks.forEach((task, taskIndex) => {
1247
1261
  task.priorityHint = taskIndex + 1;
1248
1262
  tasksOrdered.push(task);
1249
1263
  });
1250
1264
  });
1251
1265
  }
1252
- const orderedStoryIds = new Set(storiesOrdered.map((story) => story.localId));
1266
+ const orderedStoryScopes = new Set(storiesOrdered.map((story) => this.scopeStory(story)));
1253
1267
  for (const story of stories) {
1254
- if (orderedStoryIds.has(story.localId))
1268
+ if (orderedStoryScopes.has(this.scopeStory(story)))
1255
1269
  continue;
1256
1270
  storiesOrdered.push(story);
1257
1271
  }
1258
- const orderedTaskIds = new Set(tasksOrdered.map((task) => task.localId));
1272
+ const orderedTaskScopes = new Set(tasksOrdered.map((task) => this.scopeTask(task)));
1259
1273
  for (const task of tasks) {
1260
- if (orderedTaskIds.has(task.localId))
1274
+ if (orderedTaskScopes.has(this.scopeTask(task)))
1261
1275
  continue;
1262
1276
  tasksOrdered.push(task);
1263
1277
  }
1264
1278
  // Keep parent linkage intact even if malformed story references exist.
1265
1279
  for (const story of storiesOrdered) {
1266
- if (!storyByLocalId.has(story.localId))
1280
+ if (!storyByScope.has(this.scopeStory(story)))
1267
1281
  continue;
1268
- story.epicLocalId = storyByLocalId.get(story.localId)?.epicLocalId ?? story.epicLocalId;
1282
+ story.epicLocalId = storyByScope.get(this.scopeStory(story))?.epicLocalId ?? story.epicLocalId;
1269
1283
  }
1270
1284
  return { epics, stories: storiesOrdered, tasks: tasksOrdered };
1271
1285
  }
@@ -1395,9 +1409,8 @@ export class CreateTasksService {
1395
1409
  };
1396
1410
  }
1397
1411
  enforceStoryScopedDependencies(plan) {
1398
- const scopedLocalKey = (storyLocalId, localId) => `${storyLocalId}::${localId}`;
1399
1412
  const taskMap = new Map(plan.tasks.map((task) => [
1400
- scopedLocalKey(task.storyLocalId, task.localId),
1413
+ this.scopeTask(task),
1401
1414
  {
1402
1415
  ...task,
1403
1416
  dependsOnKeys: uniqueStrings((task.dependsOnKeys ?? []).filter(Boolean)),
@@ -1405,9 +1418,10 @@ export class CreateTasksService {
1405
1418
  ]));
1406
1419
  const tasksByStory = new Map();
1407
1420
  for (const task of taskMap.values()) {
1408
- const storyTasks = tasksByStory.get(task.storyLocalId) ?? [];
1421
+ const storyScope = this.storyScopeKey(task.epicLocalId, task.storyLocalId);
1422
+ const storyTasks = tasksByStory.get(storyScope) ?? [];
1409
1423
  storyTasks.push(task);
1410
- tasksByStory.set(task.storyLocalId, storyTasks);
1424
+ tasksByStory.set(storyScope, storyTasks);
1411
1425
  }
1412
1426
  for (const storyTasks of tasksByStory.values()) {
1413
1427
  const localIds = new Set(storyTasks.map((task) => task.localId));
@@ -1438,9 +1452,67 @@ export class CreateTasksService {
1438
1452
  }
1439
1453
  return {
1440
1454
  ...plan,
1441
- tasks: plan.tasks.map((task) => taskMap.get(scopedLocalKey(task.storyLocalId, task.localId)) ?? task),
1455
+ tasks: plan.tasks.map((task) => taskMap.get(this.scopeTask(task)) ?? task),
1442
1456
  };
1443
1457
  }
1458
+ validatePlanLocalIdentifiers(plan) {
1459
+ const errors = [];
1460
+ const epicIds = new Set();
1461
+ for (const epic of plan.epics) {
1462
+ if (!epic.localId || !epic.localId.trim()) {
1463
+ errors.push("epic has missing localId");
1464
+ continue;
1465
+ }
1466
+ if (epicIds.has(epic.localId)) {
1467
+ errors.push(`duplicate epic localId: ${epic.localId}`);
1468
+ continue;
1469
+ }
1470
+ epicIds.add(epic.localId);
1471
+ }
1472
+ const storyScopes = new Set();
1473
+ for (const story of plan.stories) {
1474
+ const scope = this.scopeStory(story);
1475
+ if (!epicIds.has(story.epicLocalId)) {
1476
+ errors.push(`story ${scope} references unknown epicLocalId ${story.epicLocalId}`);
1477
+ }
1478
+ if (storyScopes.has(scope)) {
1479
+ errors.push(`duplicate story scope: ${scope}`);
1480
+ continue;
1481
+ }
1482
+ storyScopes.add(scope);
1483
+ }
1484
+ const taskScopes = new Set();
1485
+ const storyTaskLocals = new Map();
1486
+ for (const task of plan.tasks) {
1487
+ const storyScope = this.storyScopeKey(task.epicLocalId, task.storyLocalId);
1488
+ const taskScope = this.scopeTask(task);
1489
+ if (!storyScopes.has(storyScope)) {
1490
+ errors.push(`task ${taskScope} references unknown story scope ${storyScope}`);
1491
+ }
1492
+ if (taskScopes.has(taskScope)) {
1493
+ errors.push(`duplicate task scope: ${taskScope}`);
1494
+ continue;
1495
+ }
1496
+ taskScopes.add(taskScope);
1497
+ const locals = storyTaskLocals.get(storyScope) ?? new Set();
1498
+ locals.add(task.localId);
1499
+ storyTaskLocals.set(storyScope, locals);
1500
+ }
1501
+ for (const task of plan.tasks) {
1502
+ const storyScope = this.storyScopeKey(task.epicLocalId, task.storyLocalId);
1503
+ const localIds = storyTaskLocals.get(storyScope) ?? new Set();
1504
+ for (const dep of task.dependsOnKeys ?? []) {
1505
+ if (!dep || dep === task.localId)
1506
+ continue;
1507
+ if (!localIds.has(dep)) {
1508
+ errors.push(`task ${this.scopeTask(task)} has dependency ${dep} that is outside story scope ${storyScope}`);
1509
+ }
1510
+ }
1511
+ }
1512
+ if (errors.length > 0) {
1513
+ throw new Error(`Invalid generated plan local identifiers:\n- ${errors.join("\n- ")}`);
1514
+ }
1515
+ }
1444
1516
  async buildQaPreflight() {
1445
1517
  const preflight = {
1446
1518
  scripts: {},
@@ -1984,13 +2056,14 @@ export class CreateTasksService {
1984
2056
  for (const story of storyMeta) {
1985
2057
  const storyId = storyIdByKey.get(story.storyKey);
1986
2058
  const existingTaskKeys = storyId ? await this.workspaceRepo.listTaskKeys(storyId) : [];
1987
- const tasks = plan.tasks.filter((t) => t.storyLocalId === story.node.localId);
2059
+ const tasks = plan.tasks.filter((t) => t.storyLocalId === story.node.localId && t.epicLocalId === story.node.epicLocalId);
1988
2060
  const taskKeyGen = createTaskKeyGenerator(story.storyKey, existingTaskKeys);
1989
2061
  for (const task of tasks) {
1990
2062
  const key = taskKeyGen();
1991
2063
  const localId = task.localId ?? key;
1992
2064
  taskDetails.push({
1993
2065
  localId,
2066
+ epicLocalId: story.node.epicLocalId,
1994
2067
  key,
1995
2068
  storyLocalId: story.node.localId,
1996
2069
  storyKey: story.storyKey,
@@ -1999,8 +2072,8 @@ export class CreateTasksService {
1999
2072
  });
2000
2073
  }
2001
2074
  }
2002
- const scopedLocalKey = (storyLocalId, localId) => `${storyLocalId}::${localId}`;
2003
- const localToKey = new Map(taskDetails.map((t) => [scopedLocalKey(t.storyLocalId, t.localId), t.key]));
2075
+ const scopedLocalKey = (epicLocalId, storyLocalId, localId) => this.taskScopeKey(epicLocalId, storyLocalId, localId);
2076
+ const localToKey = new Map(taskDetails.map((t) => [scopedLocalKey(t.epicLocalId, t.storyLocalId, t.localId), t.key]));
2004
2077
  const taskInserts = [];
2005
2078
  const testCommandBuilder = new QaTestCommandBuilder(this.workspace.workspaceRoot);
2006
2079
  for (const task of taskDetails) {
@@ -2060,7 +2133,7 @@ export class CreateTasksService {
2060
2133
  blockers: qaBlockers.length ? qaBlockers : undefined,
2061
2134
  };
2062
2135
  const depSlugs = (task.plan.dependsOnKeys ?? [])
2063
- .map((dep) => localToKey.get(scopedLocalKey(task.storyLocalId, dep)))
2136
+ .map((dep) => localToKey.get(scopedLocalKey(task.plan.epicLocalId, task.storyLocalId, dep)))
2064
2137
  .filter((value) => Boolean(value));
2065
2138
  const metadata = {
2066
2139
  doc_links: task.plan.relatedDocs ?? [],
@@ -2097,17 +2170,17 @@ export class CreateTasksService {
2097
2170
  for (const detail of taskDetails) {
2098
2171
  const row = taskRows.find((t) => t.key === detail.key);
2099
2172
  if (row) {
2100
- taskByLocal.set(scopedLocalKey(detail.storyLocalId, detail.localId), row);
2173
+ taskByLocal.set(scopedLocalKey(detail.epicLocalId, detail.storyLocalId, detail.localId), row);
2101
2174
  }
2102
2175
  }
2103
2176
  const depKeys = new Set();
2104
2177
  const dependencies = [];
2105
2178
  for (const detail of taskDetails) {
2106
- const current = taskByLocal.get(scopedLocalKey(detail.storyLocalId, detail.localId));
2179
+ const current = taskByLocal.get(scopedLocalKey(detail.epicLocalId, detail.storyLocalId, detail.localId));
2107
2180
  if (!current)
2108
2181
  continue;
2109
2182
  for (const dep of detail.plan.dependsOnKeys ?? []) {
2110
- const target = taskByLocal.get(scopedLocalKey(detail.storyLocalId, dep));
2183
+ const target = taskByLocal.get(scopedLocalKey(detail.plan.epicLocalId, detail.storyLocalId, dep));
2111
2184
  if (!target || target.id === current.id)
2112
2185
  continue;
2113
2186
  const depKey = `${current.id}|${target.id}|blocks`;
@@ -2214,6 +2287,7 @@ export class CreateTasksService {
2214
2287
  plan = this.enforceStoryScopedDependencies(plan);
2215
2288
  plan = this.applyServiceDependencySequencing(plan, docs);
2216
2289
  plan = this.enforceStoryScopedDependencies(plan);
2290
+ this.validatePlanLocalIdentifiers(plan);
2217
2291
  await this.jobService.writeCheckpoint(job.id, {
2218
2292
  stage: "stories_generated",
2219
2293
  timestamp: new Date().toISOString(),
@@ -2343,6 +2417,7 @@ export class CreateTasksService {
2343
2417
  plan = this.enforceStoryScopedDependencies(plan);
2344
2418
  plan = this.applyServiceDependencySequencing(plan, []);
2345
2419
  plan = this.enforceStoryScopedDependencies(plan);
2420
+ this.validatePlanLocalIdentifiers(plan);
2346
2421
  const loadRefinePlans = async () => {
2347
2422
  const candidates = [];
2348
2423
  if (options.refinePlanPath)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcoda/core",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
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/db": "0.1.18",
36
- "@mcoda/agents": "0.1.18",
37
- "@mcoda/integrations": "0.1.18",
38
- "@mcoda/generators": "0.1.18",
39
- "@mcoda/shared": "0.1.18"
35
+ "@mcoda/shared": "0.1.19",
36
+ "@mcoda/db": "0.1.19",
37
+ "@mcoda/agents": "0.1.19",
38
+ "@mcoda/generators": "0.1.19",
39
+ "@mcoda/integrations": "0.1.19"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsc -p tsconfig.json",