@sppg2001/atomize 1.0.1 → 1.0.3

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/cli/index.js CHANGED
@@ -98840,9 +98840,11 @@ class TemplateLoadError extends AtomizeError {
98840
98840
 
98841
98841
  class TemplateValidationError extends AtomizeError {
98842
98842
  errors;
98843
- constructor(message, errors) {
98843
+ suggestions;
98844
+ constructor(message, errors, suggestions) {
98844
98845
  super(AtomizeErrorCode.TemplateValidationError, message);
98845
98846
  this.errors = errors;
98847
+ this.suggestions = suggestions;
98846
98848
  }
98847
98849
  }
98848
98850
 
@@ -98887,9 +98889,11 @@ class CancellationError extends AtomizeError {
98887
98889
 
98888
98890
  class CircularDependencyError extends Error {
98889
98891
  cycle;
98890
- constructor(message, cycle) {
98892
+ suggestion;
98893
+ constructor(message, cycle, suggestion) {
98891
98894
  super(message);
98892
98895
  this.cycle = cycle;
98896
+ this.suggestion = suggestion;
98893
98897
  this.name = "CircularDependencyError";
98894
98898
  }
98895
98899
  }
@@ -99098,7 +99102,9 @@ class DependencyResolver {
99098
99102
  }
99099
99103
  if (visited.size !== graph.size) {
99100
99104
  const cycle = this.detectCycle(graph, visited);
99101
- throw new CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")}`, cycle);
99105
+ const cycleDisplay = cycle.join(" -> ");
99106
+ const suggestion = `Break the circular dependency by removing one of these dependencies: ${cycle.map((id, i) => i < cycle.length - 1 ? `"${id}" depends on "${cycle[i + 1]}"` : "").filter(Boolean).join(", ")}`;
99107
+ throw new CircularDependencyError(`Circular dependency detected: ${cycleDisplay}. ${suggestion}`, cycle);
99102
99108
  }
99103
99109
  return result;
99104
99110
  }
@@ -99149,11 +99155,13 @@ class DependencyResolver {
99149
99155
  validateDependencies(tasks) {
99150
99156
  const errors2 = [];
99151
99157
  const taskIds = new Set(tasks.map((t) => t.id).filter((id) => id));
99158
+ const availableIds = Array.from(taskIds);
99152
99159
  for (const task of tasks) {
99153
99160
  if (task.dependsOn && task.dependsOn.length > 0) {
99154
99161
  for (const depId of task.dependsOn) {
99155
99162
  if (!taskIds.has(depId)) {
99156
- errors2.push(`Task "${task.title}" (ID: ${task.id}) depends on non-existent task ID: "${depId}"`);
99163
+ const suggestion = availableIds.length > 0 ? ` Available task IDs: ${availableIds.map((id) => `"${id}"`).join(", ")}` : " Add an 'id' field to the task you want to depend on.";
99164
+ errors2.push(`Task "${task.title}" (ID: ${task.id}) depends on non-existent task ID: "${depId}".${suggestion}`);
99157
99165
  }
99158
99166
  }
99159
99167
  }
@@ -99472,6 +99480,9 @@ class EstimationCalculator {
99472
99480
  priority: templateTask.priority,
99473
99481
  activity: templateTask.activity,
99474
99482
  remainingWork: templateTask.remainingWork,
99483
+ completedWork: 0,
99484
+ iteration: story.iteration,
99485
+ areaPath: story.areaPath,
99475
99486
  customFields: templateTask.customFields,
99476
99487
  templateId: templateTask.id,
99477
99488
  estimationPercent: templateTask.estimationPercent
@@ -99511,6 +99522,9 @@ class EstimationCalculator {
99511
99522
  priority: templateTask.priority,
99512
99523
  activity: templateTask.activity,
99513
99524
  remainingWork: templateTask.remainingWork,
99525
+ completedWork: 0,
99526
+ iteration: story.iteration,
99527
+ areaPath: story.areaPath,
99514
99528
  customFields: templateTask.customFields,
99515
99529
  templateId: templateTask.id,
99516
99530
  estimationPercent: templateTask.estimationPercent
@@ -99547,7 +99561,7 @@ class EstimationCalculator {
99547
99561
  return 0;
99548
99562
  }
99549
99563
  roundEstimation(value, strategy) {
99550
- return M(strategy).with("up", () => Math.ceil(value)).with("down", () => Math.floor(value)).with("nearest", () => Math.round(value)).otherwise(() => Math.floor(value * 100) / 100);
99564
+ return M(strategy).with("up", () => Math.ceil(value * 2) / 2).with("down", () => Math.floor(value * 2) / 2).with("nearest", () => Math.round(value * 2) / 2).otherwise(() => Math.floor(value * 100) / 100);
99551
99565
  }
99552
99566
  interpolateTitle(title, story) {
99553
99567
  return title.replace(/\${story\.title}/g, story.title).replace(/\${story\.id}/g, story.id);
@@ -100057,6 +100071,27 @@ class AzureDevOpsAdapter {
100057
100071
  value: task.estimation
100058
100072
  }
100059
100073
  ] : [],
100074
+ ...task.completedWork !== undefined ? [
100075
+ {
100076
+ op: "add",
100077
+ path: "/fields/Microsoft.VSTS.Scheduling.CompletedWork",
100078
+ value: task.completedWork
100079
+ }
100080
+ ] : [],
100081
+ ...task.iteration ? [
100082
+ {
100083
+ op: "add",
100084
+ path: "/fields/System.IterationPath",
100085
+ value: task.iteration
100086
+ }
100087
+ ] : [],
100088
+ ...task.areaPath ? [
100089
+ {
100090
+ op: "add",
100091
+ path: "/fields/System.AreaPath",
100092
+ value: task.areaPath
100093
+ }
100094
+ ] : [],
100060
100095
  ...task.tags && task.tags.length > 0 ? [
100061
100096
  {
100062
100097
  op: "add",
@@ -100249,6 +100284,11 @@ class AzureDevOpsAdapter {
100249
100284
  const tagConditions = filter2.tags.include.map((tag) => `[System.Tags] CONTAINS '${tag}'`);
100250
100285
  conditions.push(`(${tagConditions.join(" OR ")})`);
100251
100286
  }
100287
+ if (filter2.tags?.exclude && filter2.tags.exclude.length > 0) {
100288
+ for (const tag of filter2.tags.exclude) {
100289
+ conditions.push(`[System.Tags] NOT CONTAINS '${tag}'`);
100290
+ }
100291
+ }
100252
100292
  if (filter2.areaPaths && filter2.areaPaths.length > 0) {
100253
100293
  const paths = filter2.areaPaths.map((p2) => `'${p2}'`).join(", ");
100254
100294
  conditions.push(`[System.AreaPath] IN (${paths})`);
@@ -114288,8 +114328,8 @@ var TaskDefinitionSchema = exports_external.object({
114288
114328
  id: exports_external.string().optional(),
114289
114329
  title: exports_external.string().min(1, "Task title is required"),
114290
114330
  description: exports_external.string().optional(),
114291
- estimationPercent: exports_external.number().min(0).max(100).optional(),
114292
- estimationFixed: exports_external.number().min(0).optional(),
114331
+ estimationPercent: exports_external.number().min(0, "Estimation percentage cannot be negative").max(100, "Estimation percentage cannot exceed 100").optional(),
114332
+ estimationFixed: exports_external.number().min(0, "Fixed estimation cannot be negative").optional(),
114293
114333
  estimationFormula: exports_external.string().optional(),
114294
114334
  tags: exports_external.array(exports_external.string()).optional(),
114295
114335
  condition: exports_external.string().optional(),
@@ -114360,13 +114400,15 @@ var TaskTemplateSchema = exports_external.object({
114360
114400
  const taskIds = new Set(tasks.map((t2) => t2.id).filter(Boolean));
114361
114401
  validateEstimationConstraints(v2, totalPercent, ctx);
114362
114402
  validateTaskConstraints(v2, tasks, ctx);
114403
+ const availableIds = Array.from(taskIds);
114363
114404
  tasks.forEach((task, index) => {
114364
114405
  task.dependsOn?.forEach((depId) => {
114365
114406
  if (!taskIds.has(depId)) {
114407
+ const suggestion = availableIds.length > 0 ? ` Available task IDs: ${availableIds.map((id) => `"${id}"`).join(", ")}` : "";
114366
114408
  ctx.addIssue({
114367
114409
  code: "custom",
114368
114410
  path: ["tasks", index, "dependsOn"],
114369
- message: `Task depends on non-existent task ID: "${depId}"`,
114411
+ message: `Task depends on non-existent task ID: "${depId}".${suggestion}`,
114370
114412
  params: { code: "INVALID_DEPENDENCY" }
114371
114413
  });
114372
114414
  }
@@ -114375,18 +114417,20 @@ var TaskTemplateSchema = exports_external.object({
114375
114417
  });
114376
114418
  function validateTaskConstraints(v2, tasks, ctx) {
114377
114419
  if (v2?.minTasks !== undefined && tasks.length < v2.minTasks) {
114420
+ const needed = v2.minTasks - tasks.length;
114378
114421
  ctx.addIssue({
114379
114422
  code: "custom",
114380
114423
  path: ["tasks"],
114381
- message: `Template has ${tasks.length} tasks, but minimum is ${v2.minTasks}`,
114424
+ message: `Template has ${tasks.length} task(s), but minimum is ${v2.minTasks}. Add ${needed} more task(s).`,
114382
114425
  params: { code: "TOO_FEW_TASKS" }
114383
114426
  });
114384
114427
  }
114385
114428
  if (v2?.maxTasks !== undefined && tasks.length > v2.maxTasks) {
114429
+ const excess = tasks.length - v2.maxTasks;
114386
114430
  ctx.addIssue({
114387
114431
  code: "custom",
114388
114432
  path: ["tasks"],
114389
- message: `Template has ${tasks.length} tasks, but maximum is ${v2.maxTasks}`,
114433
+ message: `Template has ${tasks.length} task(s), but maximum is ${v2.maxTasks}. Remove ${excess} task(s) or increase maxTasks.`,
114390
114434
  params: { code: "TOO_MANY_TASKS" }
114391
114435
  });
114392
114436
  }
@@ -114394,20 +114438,23 @@ function validateTaskConstraints(v2, tasks, ctx) {
114394
114438
  function validateEstimationConstraints(v2, totalPercent, ctx) {
114395
114439
  if (v2?.totalEstimationMustBe !== undefined) {
114396
114440
  if (totalPercent !== v2.totalEstimationMustBe) {
114441
+ const diff = v2.totalEstimationMustBe - totalPercent;
114442
+ const hint = diff > 0 ? ` Add ${diff}% to existing tasks.` : ` Reduce tasks by ${Math.abs(diff)}%.`;
114397
114443
  ctx.addIssue({
114398
114444
  code: "custom",
114399
114445
  path: ["tasks"],
114400
- message: `Total estimation is ${totalPercent}%, but must be ${v2.totalEstimationMustBe}%`,
114446
+ message: `Total estimation is ${totalPercent}%, but must be ${v2.totalEstimationMustBe}%.${hint}`,
114401
114447
  params: { code: "INVALID_TOTAL_ESTIMATION" }
114402
114448
  });
114403
114449
  }
114404
114450
  } else if (v2?.totalEstimationRange) {
114405
114451
  const { min, max } = v2.totalEstimationRange;
114406
114452
  if (totalPercent < min || totalPercent > max) {
114453
+ const hint = totalPercent < min ? ` Increase by ${min - totalPercent}%.` : ` Reduce by ${totalPercent - max}%.`;
114407
114454
  ctx.addIssue({
114408
114455
  code: "custom",
114409
114456
  path: ["tasks"],
114410
- message: `Total estimation is ${totalPercent}%, but must be between ${min}% and ${max}%`,
114457
+ message: `Total estimation is ${totalPercent}%, but must be between ${min}% and ${max}%.${hint}`,
114411
114458
  params: { code: "INVALID_ESTIMATION_RANGE" }
114412
114459
  });
114413
114460
  }
@@ -114428,9 +114475,12 @@ class TemplateValidator {
114428
114475
  const total = template.tasks.reduce((sum, t2) => sum + (t2.estimationPercent ?? 0), 0);
114429
114476
  const hasStrictRule = v2?.totalEstimationMustBe !== undefined || v2?.totalEstimationRange !== undefined;
114430
114477
  if (!hasStrictRule && total !== 100) {
114478
+ const diff = 100 - total;
114479
+ const suggestion = diff > 0 ? `Add ${diff}% to existing tasks or create a new task with ${diff}% estimation.` : `Reduce task estimations by ${Math.abs(diff)}% to reach 100%.`;
114431
114480
  warnings.push({
114432
114481
  path: "tasks",
114433
- message: `Total estimation is ${total}% (expected 100%).`
114482
+ message: `Total estimation is ${total}% (expected 100%).`,
114483
+ suggestion
114434
114484
  });
114435
114485
  }
114436
114486
  this.validateTaskConditions(template, warnings);
@@ -114445,7 +114495,8 @@ class TemplateValidator {
114445
114495
  if (!hasVariable && !hasOperator) {
114446
114496
  warnings.push({
114447
114497
  path: `tasks[${index}].condition`,
114448
- message: `Condition "${task.condition}" might be invalid (no variables found)`
114498
+ message: `Condition "${task.condition}" might be invalid (no variables found)`,
114499
+ suggestion: `Use variables like \${story.tags} or operators like CONTAINS, ==, !=. Example: "\${story.tags} CONTAINS 'backend'"`
114449
114500
  });
114450
114501
  }
114451
114502
  }
@@ -114456,32 +114507,110 @@ class TemplateValidator {
114456
114507
  if (task.dependsOn && task.dependsOn.length > 0 && !task.id) {
114457
114508
  warnings.push({
114458
114509
  path: `tasks[${index}]`,
114459
- message: `Task "${task.title}" has dependencies but no id field. Add an id to enable dependency linking.`
114510
+ message: `Task "${task.title}" has dependencies but no id field. Add an id to enable dependency linking.`,
114511
+ suggestion: `Add an 'id' field to this task, e.g., 'id: "${this.generateIdFromTitle(task.title)}"'`
114460
114512
  });
114461
114513
  }
114462
114514
  if (!task.id) {
114463
- const isReferenced = template.tasks.some((t2) => t2.dependsOn?.includes(task.title));
114464
- if (isReferenced) {
114515
+ const referencingTasks = template.tasks.filter((t2) => t2.dependsOn?.includes(task.title));
114516
+ if (referencingTasks.length > 0) {
114517
+ const taskNames = referencingTasks.map((t2) => `"${t2.title}"`).join(", ");
114465
114518
  warnings.push({
114466
114519
  path: `tasks[${index}]`,
114467
- message: `Task "${task.title}" is referenced by other tasks but has no id field.`
114520
+ message: `Task "${task.title}" is referenced by other tasks but has no id field.`,
114521
+ suggestion: `Add 'id: "${this.generateIdFromTitle(task.title)}"' to this task. Referenced by: ${taskNames}`
114468
114522
  });
114469
114523
  }
114470
114524
  }
114471
114525
  });
114472
114526
  }
114527
+ generateIdFromTitle(title) {
114528
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 30);
114529
+ }
114473
114530
  convertZodErrors(zodError) {
114474
- return zodError.issues.map((err) => ({
114475
- path: err.path.join("."),
114476
- message: err.message,
114477
- code: err.params?.code || err.code
114478
- }));
114531
+ return zodError.issues.map((err) => {
114532
+ const code = err.code === "custom" && typeof err.params?.code === "string" ? err.params.code : err.code;
114533
+ return {
114534
+ path: err.path.join("."),
114535
+ message: err.message,
114536
+ code,
114537
+ suggestion: this.getSuggestionForError(err, code)
114538
+ };
114539
+ });
114540
+ }
114541
+ getSuggestionForError(err, code) {
114542
+ const domainSuggestion = this.handleDomainError(err, code);
114543
+ if (domainSuggestion)
114544
+ return domainSuggestion;
114545
+ return this.handleZodError(err);
114546
+ }
114547
+ handleDomainError(err, code) {
114548
+ switch (code) {
114549
+ case "INVALID_TOTAL_ESTIMATION":
114550
+ return this.handleNumericError(err.message, /is (\d+)%, but must be (\d+)%/, (current, required2) => {
114551
+ const diff = required2 - current;
114552
+ return diff > 0 ? `Add ${diff}% to existing tasks or create a new task with ${diff}% estimation.` : `Reduce task estimations by ${Math.abs(diff)}% to reach ${required2}%.`;
114553
+ });
114554
+ case "INVALID_ESTIMATION_RANGE":
114555
+ return this.handleNumericError(err.message, /is (\d+)%, but must be between (\d+)% and (\d+)%/, (current, min, max) => {
114556
+ if (current < min)
114557
+ return `Increase task estimations by ${min - current}% to meet the minimum of ${min}%.`;
114558
+ return `Reduce task estimations by ${current - max}% to stay within the maximum of ${max}%.`;
114559
+ });
114560
+ case "TOO_FEW_TASKS":
114561
+ return this.handleNumericError(err.message, /has (\d+) tasks?, but minimum is (\d+)/, (current, required2) => `Add ${required2 - current} more task(s) to meet the minimum requirement of ${required2} tasks.`);
114562
+ case "TOO_MANY_TASKS":
114563
+ return this.handleNumericError(err.message, /has (\d+) tasks?, but maximum is (\d+)/, (current, max) => `Remove ${current - max} task(s) or increase the maxTasks limit to ${current}.`);
114564
+ case "INVALID_DEPENDENCY": {
114565
+ const match = err.message.match(/non-existent task ID: "([^"]+)"/);
114566
+ return match ? `Either add a task with id: "${match[1]}" or update the dependsOn field to reference an existing task ID.` : undefined;
114567
+ }
114568
+ default:
114569
+ return;
114570
+ }
114571
+ }
114572
+ handleNumericError(message, regex2, formatter) {
114573
+ const values = this.extractValues(message, regex2);
114574
+ if (!values || values.length === 0)
114575
+ return;
114576
+ return formatter(...values);
114577
+ }
114578
+ handleZodError(err) {
114579
+ const { code, path: path2, expected, validation } = err;
114580
+ if (code === "too_small") {
114581
+ if (path2.includes("tasks"))
114582
+ return "Add at least one task to the template.";
114583
+ if (err.minimum === 1)
114584
+ return "This field cannot be empty. Please provide a value.";
114585
+ if (path2.includes("estimationPercent"))
114586
+ return "Estimation percentage cannot be negative. Use a value between 0 and 100.";
114587
+ }
114588
+ if (code === "too_big" && path2.includes("estimationPercent")) {
114589
+ return "Estimation percentage must be between 0 and 100. Current value exceeds 100%.";
114590
+ }
114591
+ if (code === "invalid_type") {
114592
+ if (expected === "string")
114593
+ return `Expected a text value but received ${err.received}. Wrap the value in quotes.`;
114594
+ if (expected === "number")
114595
+ return `Expected a number but received ${err.received}. Remove quotes from numeric values.`;
114596
+ }
114597
+ if (code === "invalid_string" && validation === "email") {
114598
+ return 'Use a valid email address format (e.g., user@example.com) or the special value "@Me".';
114599
+ }
114600
+ return;
114601
+ }
114602
+ extractValues(message, pattern) {
114603
+ const match = message.match(pattern);
114604
+ if (!match)
114605
+ return [];
114606
+ return match.slice(1).map((val) => parseInt(val, 10));
114479
114607
  }
114480
114608
  validateOrThrow(template) {
114481
114609
  const result = this.validate(template);
114482
114610
  if (!result.valid) {
114483
114611
  const errorMessages = result.errors.map((e2) => `${e2.path}: ${e2.message}`);
114484
- throw new TemplateValidationError("Template validation failed", errorMessages);
114612
+ const suggestions = result.errors.map((e2) => e2.suggestion).filter((s2) => s2 !== undefined);
114613
+ throw new TemplateValidationError("Template validation failed", errorMessages, suggestions.length > 0 ? suggestions : undefined);
114485
114614
  }
114486
114615
  return template;
114487
114616
  }
@@ -114497,6 +114626,9 @@ class TemplateValidator {
114497
114626
  lines.push("Errors:");
114498
114627
  result.errors.forEach((err) => {
114499
114628
  lines.push(` - ${err.path}: ${err.message}`);
114629
+ if (err.suggestion) {
114630
+ lines.push(` \uD83D\uDCA1 ${err.suggestion}`);
114631
+ }
114500
114632
  });
114501
114633
  lines.push("");
114502
114634
  }
@@ -114504,6 +114636,9 @@ class TemplateValidator {
114504
114636
  lines.push("Warnings:");
114505
114637
  result.warnings.forEach((warn) => {
114506
114638
  lines.push(`${warn.path}: ${warn.message}`);
114639
+ if (warn.suggestion) {
114640
+ lines.push(` \uD83D\uDCA1 ${warn.suggestion}`);
114641
+ }
114507
114642
  });
114508
114643
  }
114509
114644
  return lines.join(`
@@ -117831,6 +117966,9 @@ async function createFromScratch(_options = {}) {
117831
117966
  console.log(source_default.gray(`Tip: Choose a clear, descriptive name for your template
117832
117967
  `));
117833
117968
  const basicInfo = await configureBasicInfo();
117969
+ if (!basicInfo.name || basicInfo.name.trim() === "") {
117970
+ throw new ConfigurationError("Template name is required");
117971
+ }
117834
117972
  const { confirmStep1 } = await dist_default12.prompt([
117835
117973
  {
117836
117974
  type: "confirm",
@@ -117840,7 +117978,7 @@ async function createFromScratch(_options = {}) {
117840
117978
  }
117841
117979
  ]);
117842
117980
  if (!confirmStep1) {
117843
- throw new Error("Template creation cancelled by user");
117981
+ throw new CancellationError("Template creation cancelled by user");
117844
117982
  }
117845
117983
  currentStep++;
117846
117984
  console.log(source_default.blue(`
@@ -117849,9 +117987,10 @@ async function createFromScratch(_options = {}) {
117849
117987
  console.log(source_default.gray(`Tip: Use filters to select which work items this template applies to
117850
117988
  `));
117851
117989
  const filterConfig = await configureFilter();
117852
- if ((!filterConfig.workItemTypes || filterConfig.workItemTypes.length === 0) && (!filterConfig.states || filterConfig.states.length === 0)) {
117990
+ if ((!filterConfig.workItemTypes || filterConfig.workItemTypes.length === 0) && (!filterConfig.states || filterConfig.states.length === 0) && !filterConfig.customQuery) {
117853
117991
  console.log(source_default.yellow(`
117854
- Warning: No work item types or states selected. Template will match all items.`));
117992
+ Warning: No work item types, states, or custom query configured.`));
117993
+ console.log(source_default.yellow(" This template will match ALL work items."));
117855
117994
  const { continueAnyway } = await dist_default12.prompt([
117856
117995
  {
117857
117996
  type: "confirm",
@@ -117861,10 +118000,7 @@ async function createFromScratch(_options = {}) {
117861
118000
  }
117862
118001
  ]);
117863
118002
  if (!continueAnyway) {
117864
- console.log(source_default.gray(`
117865
- Returning to filter configuration...
117866
- `));
117867
- throw new Error("Please configure at least work item types or states");
118003
+ throw new CancellationError("Template creation cancelled. Please configure filter criteria.");
117868
118004
  }
117869
118005
  }
117870
118006
  const { confirmStep2 } = await dist_default12.prompt([
@@ -117876,15 +118012,21 @@ Returning to filter configuration...
117876
118012
  }
117877
118013
  ]);
117878
118014
  if (!confirmStep2) {
117879
- throw new Error("Template creation cancelled by user");
118015
+ throw new CancellationError("Template creation cancelled by user");
117880
118016
  }
117881
118017
  currentStep++;
117882
118018
  console.log(source_default.blue(`
117883
118019
  [${currentStep}/${totalSteps}] Task Configuration`));
117884
118020
  console.log(source_default.gray("███░░░"));
118021
+ console.log(source_default.gray(`Tip: Break work into clear, actionable tasks
118022
+ `));
117885
118023
  const tasks = await configureTasksWithValidation();
117886
- if (tasks.length === 0) {
117887
- throw new Error("At least one task is required");
118024
+ if (!tasks || tasks.length === 0) {
118025
+ throw new ConfigurationError("At least one task is required. Please add tasks to your template.");
118026
+ }
118027
+ const invalidTasks = tasks.filter((task) => !task.title || task.title.trim() === "");
118028
+ if (invalidTasks.length > 0) {
118029
+ throw new ConfigurationError(`${invalidTasks.length} task(s) are missing titles. All tasks must have a title.`);
117888
118030
  }
117889
118031
  const { confirmStep3 } = await dist_default12.prompt([
117890
118032
  {
@@ -117895,13 +118037,13 @@ Returning to filter configuration...
117895
118037
  }
117896
118038
  ]);
117897
118039
  if (!confirmStep3) {
117898
- throw new Error("Template creation cancelled by user");
118040
+ throw new CancellationError("Template creation cancelled by user");
117899
118041
  }
117900
118042
  currentStep++;
117901
118043
  console.log(source_default.blue(`
117902
118044
  [${currentStep}/${totalSteps}] Estimation Settings`));
117903
118045
  console.log(source_default.gray("████░░"));
117904
- console.log(source_default.gray(`\uD83D\uDCA1 Tip: Choose how story points will be calculated and rounded
118046
+ console.log(source_default.gray(`Tip: Choose how story points will be calculated and rounded
117905
118047
  `));
117906
118048
  const estimation = await configureEstimation();
117907
118049
  const { confirmStep4 } = await dist_default12.prompt([
@@ -117913,7 +118055,7 @@ Returning to filter configuration...
117913
118055
  }
117914
118056
  ]);
117915
118057
  if (!confirmStep4) {
117916
- throw new Error("Template creation cancelled by user");
118058
+ throw new CancellationError("Template creation cancelled by user");
117917
118059
  }
117918
118060
  currentStep++;
117919
118061
  console.log(source_default.blue(`
@@ -117948,7 +118090,7 @@ Returning to filter configuration...
117948
118090
  console.log(source_default.blue(`
117949
118091
  [${currentStep}/${totalSteps}] Metadata (Optional)`));
117950
118092
  console.log(source_default.gray("██████"));
117951
- console.log(source_default.gray(`Tip: Metadata helps others understand when to use this template
118093
+ console.log(source_default.gray(`\uD83D\uDCA1 Tip: Metadata helps others understand when to use this template
117952
118094
  `));
117953
118095
  const { addMetadata } = await dist_default12.prompt([
117954
118096
  {
@@ -117976,23 +118118,44 @@ Returning to filter configuration...
117976
118118
  metadata
117977
118119
  };
117978
118120
  console.log(source_default.green(`
117979
- ✓ Template created successfully!
118121
+ ✓ Template configured successfully!
118122
+ `));
118123
+ console.log(source_default.gray(`Review your template and choose an action below.
117980
118124
  `));
117981
118125
  const confirmed = await previewTemplate(template);
117982
118126
  if (!confirmed) {
117983
118127
  console.log(source_default.yellow(`
117984
- Template creation cancelled. No changes were saved.`));
118128
+ Template creation cancelled. No changes were saved.`));
117985
118129
  throw new CancellationError("Template creation cancelled by user");
117986
118130
  }
118131
+ console.log(source_default.cyan(`
118132
+ \uD83D\uDCCB Final confirmation before saving...
118133
+ `));
118134
+ const { finalConfirm } = await dist_default12.prompt([
118135
+ {
118136
+ type: "confirm",
118137
+ name: "finalConfirm",
118138
+ message: "Save this template?",
118139
+ default: true
118140
+ }
118141
+ ]);
118142
+ if (!finalConfirm) {
118143
+ throw new CancellationError("Template save cancelled by user");
118144
+ }
117987
118145
  return template;
117988
118146
  } catch (error48) {
117989
118147
  if (error48 instanceof CancellationError) {
117990
118148
  console.log(source_default.yellow(`
117991
- Template creation cancelled`));
117992
- } else {
118149
+ Template creation cancelled`));
118150
+ throw error48;
118151
+ }
118152
+ if (error48 instanceof ConfigurationError) {
117993
118153
  console.log(source_default.red(`
117994
- Error creating template: ${error48.message}`));
118154
+ Configuration error: ${error48.message}`));
118155
+ console.log(source_default.gray(" Please check your inputs and try again."));
118156
+ throw error48;
117995
118157
  }
118158
+ console.log(source_default.red(` Error creating template: ${error48.message}`));
117996
118159
  throw error48;
117997
118160
  }
117998
118161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sppg2001/atomize",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Automatically generate tasks from user stories with smart templates",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",
@@ -29,7 +29,7 @@
29
29
  "prepublishOnly": "npm run build",
30
30
  "test": "bun test",
31
31
  "test:unit": "bun test tests/unit",
32
- "test:integration": "bun test tests/integration.test.ts",
32
+ "test:integration": "bun test tests/integration",
33
33
  "test:watch": "bun test --watch",
34
34
  "test:coverage": "bun test --coverage --coverage-reporter=lcov",
35
35
  "typecheck": "tsc --noEmit",
@@ -6,81 +6,111 @@ tags: []
6
6
 
7
7
  filter:
8
8
  workItemTypes: ["User Story"]
9
- states: ["New"]
10
- tags:
11
- include: ["Candidate S113"]
12
- excludeIfHasTasks: true
9
+ states: ["Ready for Sprint"]
10
+ tags:
11
+ exclude:
12
+ - "Test Only"
13
+ - "TestGen"
14
+
15
+
16
+ excludeIfHasTasks: false
17
+ areaPaths:
18
+ - EA-RSP-Platform\Release 2 Team
19
+ iterations:
20
+ - EA-RSP-Platform\SOW-7-2026-JAN-MARCH\Sprint 115
21
+
13
22
 
14
23
  tasks:
15
24
  - title: "Analysis & Playback"
16
- description: "Analyze requirements and planning of necessary changes and discuss with the team."
17
25
  estimationPercent: 20
18
26
  activity: "Development"
19
27
  tags: []
28
+ assignTo: "@ParentAssignee"
29
+
30
+
20
31
 
21
32
  - title: "Build"
22
- description: "Devolop the required changes in the codebase following best practices."
23
33
  estimationPercent: 50
24
34
  activity: "Development"
25
35
  tags: []
36
+ assignTo: "@ParentAssignee"
37
+
26
38
 
27
39
  - title: "Developer Test"
28
- description: "Testing the changes made to ensure they meet the requirements and do not introduce new issues.Provide evidence of testing"
29
40
  estimationPercent: 25
30
41
  activity: "Development"
31
42
  tags: []
43
+ assignTo: "@ParentAssignee"
44
+
32
45
 
33
46
  - title: "QA Smoke Test"
34
- description: "Ensure that main functionalities are working as expected after the deployment to the QA environment."
35
47
  estimationPercent: 0
36
48
  activity: "Development"
37
49
  tags: []
50
+ assignTo: "@ParentAssignee"
51
+
38
52
 
39
- - title: "WIKI"
40
- description: "Update relevant documentation and WIKI pages to reflect the changes made."
53
+ - title: "Wiki"
41
54
  estimationPercent: 5
42
55
  activity: "Development"
43
56
  tags: []
57
+ assignTo: "@ParentAssignee"
58
+
44
59
 
45
60
  - title: "Code Review "
46
- description: "Address code review feedback and refactor as needed"
47
61
  estimationPercent: 0
48
62
  activity: "Development"
49
63
  tags: []
64
+ assignTo: "@Unassigned"
50
65
 
51
66
  - title: "Test Preparation"
52
- description: "Prepare test cases and scenarios for QA testing."
53
67
  estimationPercent: 40
54
68
  activity: "Testing"
55
69
  tags: []
70
+ condition: '${story.tags} NOT CONTAINS "Dev Only"'
71
+ assignTo: "@Unassigned"
72
+
73
+
56
74
 
57
75
  - title: "Test Execution"
58
- description: "Execute test cases and report any defects found during testing."
59
76
  estimationPercent: 27
60
77
  activity: "Testing"
61
78
  tags: []
79
+ condition: '${story.tags} NOT CONTAINS "Dev Only"'
80
+ assignTo: "@Unassigned"
81
+
82
+
62
83
 
63
84
  - title: "Automation Testing"
64
- description: "Develop and maintain automated test scripts to improve testing efficiency."
65
85
  estimationPercent: 0
66
86
  activity: "Testing"
67
87
  tags: []
88
+ condition: '${story.tags} NOT CONTAINS "Dev Only"'
89
+ assignTo: "@Unassigned"
90
+
91
+
68
92
 
69
93
  - title: "Test Review"
70
- description: "Review test results and ensure all requirements are met before deployment."
71
94
  estimationPercent: 0
72
95
  activity: "Testing"
73
96
  tags: []
97
+ condition: '${story.tags} NOT CONTAINS "Dev Only"'
98
+ assignTo: "@Unassigned"
99
+
100
+
74
101
 
75
102
  - title: "Release Notes"
76
- description: "Prepare release notes and documentation for the deployment."
77
103
  estimationPercent: 0
78
- activity: "Documentation"
104
+ activity: "Testing"
79
105
  tags: []
106
+ condition: '${story.tags} NOT CONTAINS "Dev Only"'
107
+ assignTo: "@Unassigned"
108
+
109
+
80
110
 
81
111
  estimation:
82
112
  strategy: "percentage"
83
- rounding: "nearest"
113
+ rounding: "none"
84
114
  minimumTaskPoints: 0
85
115
 
86
116
  metadata: