@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 +206 -43
- package/package.json +2 -2
- package/templates/presets/custom.yaml +48 -18
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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}
|
|
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
|
|
114464
|
-
if (
|
|
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
|
-
|
|
114476
|
-
|
|
114477
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
117992
|
-
|
|
118149
|
+
Template creation cancelled`));
|
|
118150
|
+
throw error48;
|
|
118151
|
+
}
|
|
118152
|
+
if (error48 instanceof ConfigurationError) {
|
|
117993
118153
|
console.log(source_default.red(`
|
|
117994
|
-
|
|
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.
|
|
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
|
|
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: ["
|
|
10
|
-
tags:
|
|
11
|
-
|
|
12
|
-
|
|
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: "
|
|
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: "
|
|
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: "
|
|
113
|
+
rounding: "none"
|
|
84
114
|
minimumTaskPoints: 0
|
|
85
115
|
|
|
86
116
|
metadata:
|