@sppg2001/atomize 1.2.4 → 2.0.0

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.
@@ -0,0 +1,743 @@
1
+ # Template Reference
2
+
3
+ Complete reference for Atomize YAML template files.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Top-Level Fields](#top-level-fields)
9
+ - [Composition](#composition)
10
+ - [filter](#filter)
11
+ - [tasks](#tasks)
12
+ - [Task Fields](#task-fields)
13
+ - [Variable Interpolation](#variable-interpolation)
14
+ - [Assignment Patterns](#assignment-patterns)
15
+ - [Task Custom Fields](#task-custom-fields)
16
+ - [Conditional Tasks](#conditional-tasks)
17
+ - [Conditional Estimation](#conditional-estimation)
18
+ - [Task Dependencies](#task-dependencies)
19
+ - [estimation](#estimation)
20
+ - [validation](#validation)
21
+ - [metadata](#metadata)
22
+ - [Complete Example](#complete-example)
23
+
24
+ ---
25
+
26
+ ## Overview
27
+
28
+ Templates are YAML files that define how Atomize breaks down user stories into tasks. Each template specifies:
29
+
30
+ 1. **Which work items to target** (via `filter`)
31
+ 2. **What tasks to create** (via `tasks`)
32
+ 3. **How to distribute estimations** (via `estimation`)
33
+ 4. **Optional constraints** (via `validation`)
34
+ 5. **Optional metadata** (via `metadata`)
35
+
36
+ ---
37
+
38
+ ## Top-Level Fields
39
+
40
+ ```yaml
41
+ version: "1.0" # Required. Always "1.0"
42
+ name: "Template Name" # Required. Human-readable name (max 200 chars)
43
+ description: "..." # Optional. What this template is for (max 500 chars)
44
+ author: "Your Name" # Optional. Author name or team
45
+ tags: ["tag1", "tag2"] # Optional. Categorization tags
46
+ extends: "backend-api" # Optional. Inherit from a catalog template
47
+ mixins: # Optional. Mix in reusable task groups
48
+ - "security-review-tasks"
49
+ - "release-checklist"
50
+ ```
51
+
52
+ | Field | Required | Type | Description |
53
+ |-------|----------|------|-------------|
54
+ | `version` | Yes | string | Template schema version. Use `"1.0"` |
55
+ | `name` | Yes | string | Template display name |
56
+ | `description` | No | string | Detailed description |
57
+ | `author` | No | string | Author name or team |
58
+ | `tags` | No | string[] | Tags for categorization |
59
+ | `extends` | No | string | Catalog template name or file path to inherit from |
60
+ | `mixins` | No | string[] | Catalog mixin names or file paths to merge into this template |
61
+
62
+ ---
63
+
64
+ ## Composition
65
+
66
+ Templates can inherit from other templates and mix in reusable task groups. This lets you build a library of building blocks and assemble them without duplication.
67
+
68
+ ### Inheritance (`extends`)
69
+
70
+ A template that declares `extends` inherits the `filter`, `tasks`, `estimation`, `validation`, and `metadata` of its base. Fields defined in the child override the corresponding fields in the base.
71
+
72
+ ```yaml
73
+ version: "1.0"
74
+ name: "Secure Backend API"
75
+ extends: "backend-api" # inherits all tasks and filter from backend-api
76
+
77
+ tasks:
78
+ - id: "pen-test"
79
+ title: "Penetration Testing"
80
+ estimationPercent: 10
81
+ activity: "Testing"
82
+ ```
83
+
84
+ The resolved template merges the child's `tasks` list on top of the base's, then re-validates the combined result. Use `atomize template resolve` to see the merged output before using it.
85
+
86
+ ### Mixins (`mixins`)
87
+
88
+ A mixin is a partial template — typically a group of related tasks — that can be mixed into any template. The tasks from all listed mixins are appended to the template's own task list before validation.
89
+
90
+ ```yaml
91
+ version: "1.0"
92
+ name: "Feature With Release Checks"
93
+ extends: "feature"
94
+ mixins:
95
+ - "security-review-tasks"
96
+ - "release-checklist"
97
+ ```
98
+
99
+ Mixins are installed and managed with the `template` commands:
100
+
101
+ ```bash
102
+ # Install a mixin from a URL
103
+ atomize template install https://example.com/mixins/security-tasks.yaml
104
+
105
+ # List available mixins
106
+ atomize template list --type mixin
107
+
108
+ # Create a new mixin
109
+ atomize template create --type mixin --save-as security-review-tasks
110
+ ```
111
+
112
+ ### Resolution order
113
+
114
+ When Atomize processes a composed template:
115
+ 1. Base template (`extends`) is loaded and fully resolved first
116
+ 2. Mixin task lists are appended in declaration order
117
+ 3. Child template fields override the merged base
118
+ 4. The final result is validated as a single flat template
119
+
120
+ Use `atomize template resolve <template>` to inspect the final merged YAML at any point.
121
+
122
+ ### Compatibility
123
+
124
+ Templates using `extends` or `mixins` require Atomize v2.0.0 or later. Older versions will reject them with a schema error. If you share composed templates with users on older versions, they must upgrade first or you can share the resolved output from `atomize template resolve --quiet > resolved.yaml`.
125
+
126
+ ---
127
+
128
+ ## filter
129
+
130
+ Defines which work items this template applies to. All criteria are combined with AND logic.
131
+
132
+ ```yaml
133
+ filter:
134
+ team: "Backend Team" # Override team from the selected profile
135
+ workItemTypes: ["User Story", "Bug"]
136
+ states: ["New", "Active"]
137
+ statesExclude: ["Done", "Removed"] # Exclude items in these states
138
+ statesWereEver: ["In Review"] # Items that were ever in these states
139
+ tags:
140
+ include: ["backend"]
141
+ exclude: ["deprecated"]
142
+ areaPaths: ["MyProject\\Backend"]
143
+ areaPathsUnder: ["MyProject\\Backend"] # Match area path and all descendants
144
+ iterations: ["MyProject\\Sprint 23"]
145
+ iterationsUnder: ["MyProject\\Release 2"] # Match iteration and all descendants
146
+ assignedTo: ["user@company.com", "@Me"]
147
+ changedAfter: "@Today-7" # Changed in the last 7 days
148
+ createdAfter: "@Today-30" # Created in the last 30 days
149
+ priority:
150
+ min: 1
151
+ max: 3
152
+ excludeIfHasTasks: true
153
+ ```
154
+
155
+ ### Filter Fields
156
+
157
+ | Field | Type | Description |
158
+ |-------|------|-------------|
159
+ | `team` | string | Override the team from the selected Azure DevOps profile for this template |
160
+ | `workItemTypes` | string[] | Work item types to match (e.g., `"User Story"`, `"Bug"`) |
161
+ | `states` | string[] | Work item states to include (e.g., `"New"`, `"Active"`) |
162
+ | `statesExclude` | string[] | Work item states to exclude (e.g., `"Done"`, `"Removed"`) |
163
+ | `statesWereEver` | string[] | Match items that were ever in these states |
164
+ | `tags.include` | string[] | Must have at least one of these tags |
165
+ | `tags.exclude` | string[] | Must not have any of these tags |
166
+ | `areaPaths` | string[] | Area paths to match exactly (case-sensitive). Use `"@TeamAreas"` for the team's areas |
167
+ | `areaPathsUnder` | string[] | Area paths — matches the path and all descendants (UNDER query) |
168
+ | `iterations` | string[] | Iteration paths to match. Use `"@CurrentIteration"` for the team's current sprint |
169
+ | `iterationsUnder` | string[] | Iteration paths — matches the iteration and all descendants (UNDER query) |
170
+ | `assignedTo` | string[] | Assigned user emails, or `"@Me"` for the current user |
171
+ | `changedAfter` | string | Only match items changed on or after this date. Supports `@Today` macros |
172
+ | `createdAfter` | string | Only match items created on or after this date. Supports `@Today` macros |
173
+ | `priority.min` | number | Minimum priority (1 = highest) |
174
+ | `priority.max` | number | Maximum priority |
175
+ | `excludeIfHasTasks` | boolean | Skip work items that already have child tasks |
176
+
177
+ ### Special Values
178
+
179
+ Some filter fields accept special macro values:
180
+
181
+ | Value | Used in | Behavior |
182
+ |-------|---------|----------|
183
+ | `"@Me"` | `assignedTo` | Resolves to the currently authenticated user's email |
184
+ | `"@TeamAreas"` | `areaPaths` | Resolves to all area paths owned by the configured team |
185
+ | `"@CurrentIteration"` | `iterations` | Resolves to the team's current sprint/iteration |
186
+
187
+ ### Date Filters
188
+
189
+ `changedAfter` and `createdAfter` accept either a literal date string or an `@Today` macro for relative dates.
190
+
191
+ **`@Today` macro syntax:** `@Today`, `@Today-N`, `@Today+N`
192
+
193
+ ```yaml
194
+ filter:
195
+ changedAfter: "@Today-7" # Changed in the last 7 days
196
+ changedAfter: "@Today-30" # Changed in the last 30 days
197
+ createdAfter: "@Today" # Created today
198
+ createdAfter: "2026-01-01" # Created on or after a specific date
199
+ ```
200
+
201
+ **Other supported macros:** `@StartOfDay`, `@StartOfWeek`, `@StartOfMonth`, `@StartOfYear` (also support `±N` offsets)
202
+
203
+ ### Area Paths: exact vs. descendants
204
+
205
+ Use `areaPaths` for exact matches and `areaPathsUnder` to include all sub-areas:
206
+
207
+ ```yaml
208
+ # Only items directly in "MyProject\Backend"
209
+ filter:
210
+ areaPaths: ["MyProject\\Backend"]
211
+
212
+ # Items in "MyProject\Backend" AND any sub-area (e.g. "MyProject\Backend\API")
213
+ filter:
214
+ areaPathsUnder: ["MyProject\\Backend"]
215
+ ```
216
+
217
+ ### Iterations: exact vs. descendants
218
+
219
+ Use `iterations` for exact matches and `iterationsUnder` to include all child iterations:
220
+
221
+ ```yaml
222
+ # Only items in Sprint 23
223
+ filter:
224
+ iterations: ["MyProject\\Sprint 23"]
225
+
226
+ # All sprints under Release 2 (e.g. Sprint 1, Sprint 2, ...)
227
+ filter:
228
+ iterationsUnder: ["MyProject\\Release 2"]
229
+ ```
230
+
231
+ ### savedQuery
232
+
233
+ Delegate query composition entirely to an existing Azure DevOps saved query. Atomize resolves the query via the ADO Queries API, executes its WIQL, and pipes the results into the standard task-creation pipeline.
234
+
235
+ Reference by ID (most stable — unaffected by renames or moves):
236
+
237
+ ```yaml
238
+ filter:
239
+ savedQuery:
240
+ id: "a1b2c3d4-0000-0000-0000-000000000000"
241
+ ```
242
+
243
+ Reference by path (human-readable, matches the query browser hierarchy):
244
+
245
+ ```yaml
246
+ filter:
247
+ savedQuery:
248
+ path: "Shared Queries/Teams/Backend Team/Sprint Active Stories"
249
+ ```
250
+
251
+ **Rules:**
252
+
253
+ - `id` and `path` are mutually exclusive — provide exactly one.
254
+ - `id` must be a valid UUID (visible in the ADO query URL).
255
+ - Only **flat (Work Items)** queries are supported. Tree and one-hop queries are rejected at runtime with a clear error.
256
+ - `savedQuery` takes precedence over all structured filter fields (`workItemTypes`, `states`, `tags`, etc.). A validator warning is emitted if both are present.
257
+
258
+ **Post-processing still applies:** `excludeIfHasTasks` still applies to the query results after resolution, and you can still cap execution with the CLI `--limit` option.
259
+
260
+ ```yaml
261
+ filter:
262
+ savedQuery:
263
+ path: "Shared Queries/All Active Stories"
264
+ excludeIfHasTasks: true
265
+ ```
266
+
267
+ ```bash
268
+ atomize generate my-template.yaml --limit 20
269
+ ```
270
+
271
+ **Permission requirements:**
272
+
273
+ The PAT used by Atomize must have **Work Items (Read)** scope on the project containing the query. Only shared queries are accessible — private queries owned by another user will return a "Query not found" error even if the ID is correct.
274
+
275
+ **Permission error reference:**
276
+
277
+ | Scenario | Error |
278
+ |----------|-------|
279
+ | Query ID or path does not exist | `Query not found: "...". Verify the ID or path and that the query has been shared with your account.` |
280
+ | PAT lacks read permission | `Access denied to query "...". Ensure your PAT includes the Work Items (Read) scope.` |
281
+ | Resolved path is a folder | `"..." is a query folder, not a runnable query.` |
282
+ | Tree or one-hop query type | `Only flat (Work Items) queries are supported. "..." is a tree or one-hop query.` |
283
+
284
+ **Tip:** Use `atomize queries list` to browse available query paths and IDs without leaving the terminal.
285
+
286
+ ---
287
+
288
+ ## tasks
289
+
290
+ Defines the tasks to create for each matching work item. Tasks are created as child items.
291
+
292
+ ```yaml
293
+ tasks:
294
+ - id: "design"
295
+ title: "Design: ${story.title}"
296
+ description: "Design and planning phase"
297
+ estimationPercent: 20
298
+ activity: "Design"
299
+ tags: ["design"]
300
+ assignTo: "@ParentAssignee"
301
+ priority: 2
302
+ condition:
303
+ field: "tags"
304
+ operator: "contains"
305
+ value: "backend"
306
+ dependsOn: []
307
+ acceptanceCriteria:
308
+ - "Criteria 1"
309
+ - "Criteria 2"
310
+ ```
311
+
312
+ ### Task Fields
313
+
314
+ | Field | Required | Type | Description |
315
+ |-------|----------|------|-------------|
316
+ | `title` | Yes | string | Task title (max 500 chars). Supports variable interpolation |
317
+ | `id` | No | string | Unique identifier for dependencies (max 30 chars) |
318
+ | `description` | No | string | Task description (max 2000 chars) |
319
+ | `estimationPercent` | No | number | Percentage of parent story points (0-100) |
320
+ | `estimationFixed` | No | number | Fixed point value regardless of parent estimation |
321
+ | `estimationPercentCondition` | No | array | Conditional estimation rules (see below) |
322
+ | `activity` | No | string | Activity type (e.g., `"Development"`, `"Testing"`, `"Design"`) |
323
+ | `tags` | No | string[] | Tags to apply to the task |
324
+ | `assignTo` | No | string | Who to assign the task to (see assignment patterns) |
325
+ | `priority` | No | number | Task priority (1-4) |
326
+ | `condition` | No | object | Structured condition to conditionally create this task |
327
+ | `dependsOn` | No | string[] | IDs of tasks this task depends on |
328
+ | `acceptanceCriteria` | No | string[] | List of acceptance criteria |
329
+ | `customFields` | No | object | Task-level Azure DevOps field values keyed by reference name |
330
+
331
+ ### Variable Interpolation
332
+
333
+ Use story data dynamically in task titles and descriptions:
334
+
335
+ | Variable | Description |
336
+ |----------|-------------|
337
+ | `${story.title}` | Story title |
338
+ | `${story.id}` | Story ID (e.g., `STORY-123`) |
339
+ | `${story.description}` | Story description |
340
+ | `${story.estimation}` | Story points |
341
+ | `${story.tags}` | Story tags (semicolon-separated) |
342
+
343
+ **Example:**
344
+
345
+ ```yaml
346
+ tasks:
347
+ - title: "Design API: ${story.title}"
348
+ description: |
349
+ Design the REST API for: ${story.title}
350
+
351
+ Story ID: ${story.id}
352
+ Estimation: ${story.estimation} points
353
+ ```
354
+
355
+ Task custom field values also support story custom-field interpolation using moustache syntax:
356
+
357
+ ```yaml
358
+ tasks:
359
+ - title: "Backend Implementation"
360
+ customFields:
361
+ Custom.ReleaseVersion: "{{ story.customFields['Custom.ReleaseVersion'] }}"
362
+ ```
363
+
364
+ ### Assignment Patterns
365
+
366
+ The `assignTo` field supports special patterns in addition to email addresses:
367
+
368
+ | Value | Behavior |
369
+ |-------|----------|
370
+ | `@ParentAssignee` | Inherit assignment from the parent story |
371
+ | `@Inherit` | Same as `@ParentAssignee` |
372
+ | `@Me` | Assign to the currently authenticated user |
373
+ | `@Unassigned` | Leave the task unassigned |
374
+ | `"user@company.com"` | Assign to a specific user by email |
375
+
376
+ ```yaml
377
+ tasks:
378
+ - title: "Implementation"
379
+ assignTo: "@ParentAssignee" # Inherit from story
380
+
381
+ - title: "Code Review"
382
+ assignTo: "lead@company.com" # Specific person
383
+
384
+ - title: "Testing"
385
+ assignTo: "@Me" # Whoever runs the command
386
+ ```
387
+
388
+ ### Task Custom Fields
389
+
390
+ Use `customFields` to set Azure DevOps fields when Atomize creates tasks. Keys are always ADO reference names such as `Custom.ClientTier` or `Microsoft.VSTS.Common.Priority`.
391
+
392
+ ```yaml
393
+ tasks:
394
+ - id: "backend"
395
+ title: "Backend Implementation"
396
+ customFields:
397
+ Custom.ClientTier: "Enterprise"
398
+ Custom.ReleaseVersion: "{{ story.customFields['Custom.ReleaseVersion'] }}"
399
+ Custom.IsBillable: true
400
+ Custom.TierRank: 3
401
+ Custom.ReleaseDate: "@Today"
402
+ ```
403
+
404
+ **Supported value types:**
405
+
406
+ | ADO field type | Template value type | Notes |
407
+ |----------------|---------------------|-------|
408
+ | Text (single line) | string | Free-form string |
409
+ | Text (multi-line) | string | Multi-line values are allowed |
410
+ | Boolean | boolean | `true` or `false` |
411
+ | Date/Time | string | ISO 8601 date/time or supported macros such as `@Today` |
412
+ | Decimal | number | Floating-point values |
413
+ | Identity | string | Email address or values such as `@Me` when supported by ADO |
414
+ | Integer | number | Whole-number values |
415
+ | Picklist (String) | string | Must match one of the allowed values |
416
+ | Picklist (Integer) | number | Must match one of the allowed values |
417
+
418
+ **Validation behavior:**
419
+ - `atomize validate` without `--profile` checks structure only and warns that custom fields were not verified against ADO
420
+ - `atomize validate --profile <name>` connects to ADO and validates field names, read-only status, types, and picklist values
421
+ - `atomize generate` always validates custom fields against live ADO field metadata before creating work items
422
+
423
+ Use `atomize fields list` to browse available field names and types for a project before authoring templates.
424
+
425
+ ### Conditional Tasks
426
+
427
+ Tasks with a `condition` are only created when the condition evaluates to `true`. Conditions are structured objects evaluated against the parent story at generate time. When a conditional task is skipped, its estimation is redistributed to other tasks proportionally.
428
+
429
+ ```yaml
430
+ tasks:
431
+ - title: "Security Review"
432
+ estimationPercent: 10
433
+ condition:
434
+ field: "tags"
435
+ operator: "contains"
436
+ value: "security"
437
+
438
+ - title: "Database Migration"
439
+ estimationPercent: 15
440
+ condition:
441
+ all:
442
+ - field: "tags"
443
+ operator: "contains"
444
+ value: "database"
445
+ - field: "estimation"
446
+ operator: "gt"
447
+ value: 5
448
+ ```
449
+
450
+ **Simple clause fields:**
451
+
452
+ | Property | Description |
453
+ |----------|-------------|
454
+ | `field` | Built-in story field such as `title`, `tags`, or `estimation` |
455
+ | `customField` | Parent story custom field reference name such as `Custom.ClientTier` |
456
+ | `operator` | One of `equals`, `not-equals`, `contains`, `not-contains`, `gt`, `lt`, `gte`, `lte` |
457
+ | `value` | String, number, or boolean to compare against |
458
+
459
+ **Compound clauses:**
460
+ - `{ all: [...] }` means every nested clause must match
461
+ - `{ any: [...] }` means at least one nested clause must match
462
+
463
+ ```yaml
464
+ tasks:
465
+ - title: "Enterprise Review"
466
+ condition:
467
+ customField: "Custom.ClientTier"
468
+ operator: "equals"
469
+ value: "Enterprise"
470
+ ```
471
+
472
+ ### Conditional Estimation
473
+
474
+ Use `estimationPercentCondition` to adapt a task's percentage based on story properties. Rules are evaluated in order; the first matching rule wins. The `estimationPercent` field serves as the fallback.
475
+
476
+ ```yaml
477
+ tasks:
478
+ - title: "Implementation"
479
+ estimationPercent: 50 # Default/fallback
480
+ estimationPercentCondition:
481
+ - condition:
482
+ field: "tags"
483
+ operator: "contains"
484
+ value: "critical"
485
+ percent: 60 # Higher weight for critical stories
486
+ - condition:
487
+ field: "estimation"
488
+ operator: "gte"
489
+ value: 13
490
+ percent: 55 # More work for large stories
491
+ - condition:
492
+ field: "tags"
493
+ operator: "contains"
494
+ value: "fullstack"
495
+ percent: 40 # Less backend work for fullstack stories
496
+ ```
497
+
498
+ **Key behaviors:**
499
+ - Rules are evaluated in order; first match wins
500
+ - If no condition matches, `estimationPercent` is used
501
+ - Normalization uses the resolved percentages (including skipped tasks)
502
+ - Conditional percentages participate correctly in the 100% distribution
503
+
504
+ ### Task Dependencies
505
+
506
+ Define execution order by referencing other task IDs in `dependsOn`. Atomize creates dependency links between tasks in the platform.
507
+
508
+ ```yaml
509
+ tasks:
510
+ - id: "design"
511
+ title: "Design API"
512
+ estimationPercent: 15
513
+
514
+ - id: "implement"
515
+ title: "Implement API"
516
+ estimationPercent: 50
517
+ dependsOn: ["design"] # Cannot start until "design" is done
518
+
519
+ - id: "test"
520
+ title: "Write Tests"
521
+ estimationPercent: 25
522
+ dependsOn: ["implement"]
523
+
524
+ - id: "review"
525
+ title: "Code Review & Documentation"
526
+ estimationPercent: 10
527
+ dependsOn: ["implement", "test"] # Multiple dependencies
528
+ ```
529
+
530
+ **Rules:**
531
+ - `id` is required on any task that other tasks depend on
532
+ - `id` is required on any task that uses `dependsOn`
533
+ - Circular dependencies are a validation error
534
+ - IDs must be unique within a template
535
+
536
+ ---
537
+
538
+ ## estimation
539
+
540
+ Controls how story points are distributed across tasks.
541
+
542
+ ```yaml
543
+ estimation:
544
+ strategy: "percentage" # How to calculate task points
545
+ rounding: "nearest" # How to round calculated values
546
+ minimumTaskPoints: 0.5 # Minimum points per task
547
+ ifParentHasNoEstimation: "skip" # What to do if parent has no points
548
+ defaultParentEstimation: 8 # Used when parent has no estimation (if not "skip")
549
+ ```
550
+
551
+ | Field | Type | Default | Options | Description |
552
+ |-------|------|---------|---------|-------------|
553
+ | `strategy` | string | `"percentage"` | `"percentage"` | How to calculate task estimations |
554
+ | `rounding` | string | `"nearest"` | `nearest`, `up`, `down`, `none` | How to round decimal point values |
555
+ | `minimumTaskPoints` | number | `0` | any non-negative number | Minimum points for any task |
556
+ | `ifParentHasNoEstimation` | string | `"skip"` | `skip`, `warn`, `use-default` | Behavior when parent story has no estimation |
557
+ | `defaultParentEstimation` | number | `8` | any positive number | Fallback estimation when parent has none |
558
+
559
+ **Rounding options:**
560
+ - `nearest` - Round to nearest whole number (0.5 → 1, 0.4 → 0)
561
+ - `up` - Always round up (0.1 → 1)
562
+ - `down` - Always round down (0.9 → 0)
563
+ - `none` - Keep decimal values
564
+
565
+ ---
566
+
567
+ ## validation
568
+
569
+ Optional rules that validate the template before use. See [Validation Modes](./Validation-Modes.md) for details on strict vs lenient behavior.
570
+
571
+ ```yaml
572
+ validation:
573
+ mode: "lenient" # lenient or strict
574
+ totalEstimationMustBe: 100 # Total must equal exactly this
575
+ totalEstimationRange: # OR use a range instead
576
+ min: 95
577
+ max: 105
578
+ minTasks: 3 # Minimum number of tasks required
579
+ maxTasks: 10 # Maximum number of tasks allowed
580
+ taskEstimationRange: # Each task must fall in this range
581
+ min: 0.5
582
+ max: 8
583
+ requiredTasks: # Tasks that must be present
584
+ - title: "Code Review"
585
+ id: "review"
586
+ ```
587
+
588
+ | Field | Type | Description |
589
+ |-------|------|-------------|
590
+ | `mode` | string | `"lenient"` (default) or `"strict"`. In strict mode, warnings become errors |
591
+ | `totalEstimationMustBe` | number | Total estimation percentage must equal this value |
592
+ | `totalEstimationRange` | object | Total estimation must fall within `min`-`max` range |
593
+ | `minTasks` | number | Minimum number of tasks required |
594
+ | `maxTasks` | number | Maximum number of tasks allowed |
595
+ | `taskEstimationRange` | object | Each individual task's resolved estimation must fall within this range |
596
+ | `requiredTasks` | array | Tasks that must exist (matched by `id` or `title`) |
597
+
598
+ > **Note:** `totalEstimationMustBe` and `totalEstimationRange` are mutually exclusive.
599
+
600
+ ---
601
+
602
+ ## metadata
603
+
604
+ Optional metadata to help others discover and use your template.
605
+
606
+ ```yaml
607
+ metadata:
608
+ category: "Backend"
609
+ difficulty: "intermediate" # beginner, intermediate, advanced
610
+ recommendedFor:
611
+ - "API development"
612
+ - "Microservices"
613
+ estimationGuidelines: "Based on typical backend API workflows for 5-13 point stories."
614
+ examples:
615
+ - "Implement user authentication endpoint"
616
+ - "Build REST API for product catalog"
617
+ ```
618
+
619
+ | Field | Type | Description |
620
+ |-------|------|-------------|
621
+ | `category` | string | Template category (e.g., `"Backend"`, `"Frontend"`) |
622
+ | `difficulty` | string | `"beginner"`, `"intermediate"`, or `"advanced"` |
623
+ | `recommendedFor` | string[] | Descriptions of when to use this template |
624
+ | `estimationGuidelines` | string | Guidance on estimation sizing for this template |
625
+ | `examples` | string[] | Example story titles this template works well for |
626
+
627
+ ---
628
+
629
+ ## Complete Example
630
+
631
+ ```yaml
632
+ version: "1.0"
633
+ name: "Backend API Development"
634
+ description: "Standard workflow for developing RESTful API endpoints"
635
+ author: "Platform Team"
636
+ tags: ["backend", "api", "rest"]
637
+
638
+ filter:
639
+ workItemTypes: ["User Story"]
640
+ states: ["New", "Active"]
641
+ tags:
642
+ include: ["backend", "api"]
643
+ exclude: ["deprecated"]
644
+ priority:
645
+ min: 1
646
+ max: 3
647
+ excludeIfHasTasks: true
648
+
649
+ tasks:
650
+ - id: "design"
651
+ title: "Design API: ${story.title}"
652
+ description: "Design REST endpoints, request/response schemas, and error handling"
653
+ estimationPercent: 15
654
+ activity: "Design"
655
+ tags: ["design", "api"]
656
+ assignTo: "@ParentAssignee"
657
+
658
+ - id: "db-schema"
659
+ title: "Database Schema"
660
+ description: "Create or update database schema and migrations"
661
+ estimationPercent: 15
662
+ estimationPercentCondition:
663
+ - condition:
664
+ field: "tags"
665
+ operator: "contains"
666
+ value: "complex-db"
667
+ percent: 25
668
+ activity: "Development"
669
+ dependsOn: ["design"]
670
+
671
+ - id: "implement"
672
+ title: "Implement Core Logic: ${story.title}"
673
+ description: "Implement business logic, validation, and API endpoints"
674
+ estimationPercent: 35
675
+ activity: "Development"
676
+ assignTo: "@ParentAssignee"
677
+ dependsOn: ["design", "db-schema"]
678
+
679
+ - id: "unit-tests"
680
+ title: "Unit Tests"
681
+ estimationPercent: 15
682
+ activity: "Testing"
683
+ dependsOn: ["implement"]
684
+
685
+ - id: "integration-tests"
686
+ title: "Integration Tests"
687
+ estimationPercent: 10
688
+ activity: "Testing"
689
+ dependsOn: ["implement"]
690
+
691
+ - id: "security-review"
692
+ title: "Security Review"
693
+ estimationPercent: 10
694
+ activity: "Testing"
695
+ condition:
696
+ any:
697
+ - field: "tags"
698
+ operator: "contains"
699
+ value: "security"
700
+ - field: "tags"
701
+ operator: "contains"
702
+ value: "auth"
703
+ dependsOn: ["implement"]
704
+
705
+ - id: "review"
706
+ title: "Code Review & Documentation"
707
+ estimationPercent: 10
708
+ activity: "Documentation"
709
+ assignTo: "@Me"
710
+ dependsOn: ["unit-tests", "integration-tests"]
711
+
712
+ estimation:
713
+ strategy: "percentage"
714
+ rounding: "nearest"
715
+ minimumTaskPoints: 0.5
716
+ ifParentHasNoEstimation: "skip"
717
+
718
+ validation:
719
+ mode: "strict"
720
+ totalEstimationMustBe: 100
721
+ minTasks: 3
722
+ maxTasks: 10
723
+
724
+ metadata:
725
+ category: "Backend"
726
+ difficulty: "intermediate"
727
+ recommendedFor:
728
+ - "REST API development"
729
+ - "Microservice endpoints"
730
+ - "Data service implementation"
731
+ estimationGuidelines: "Works best with stories between 5-13 story points."
732
+ ```
733
+
734
+ ---
735
+
736
+ ## See Also
737
+
738
+ - [CLI Reference](./Cli-Reference.md) - Commands for validating, installing, and using templates
739
+ - [Validation Modes](./Validation-Modes.md) - Strict vs lenient validation explained
740
+ - [Common Validation Errors](./Common-Validation-Errors.md) - Fix validation failures
741
+ - [Story Learner](./Story-Learner.md) - Generate templates from existing work items
742
+ - [Template Wizard Guide](./template-wizard-guide.md) - Interactive template creation
743
+ - [Examples](../examples/) - Real-world template examples