| name | devops:workitem |
| description | Azure DevOps work item management with subcommands for CRUD operations, querying, and bidirectional sync between local agile artifacts and Azure DevOps. Use this skill whenever the user wants to interact with Azure DevOps work items — creating, reading, updating, searching, listing, or syncing. Triggers on: "devops workitem", "get workitem", "create workitem", "update workitem", "search workitem", "list workitems", "push to devops", "pull from devops", "sync devops", "show sprints", "show areas", "show tags", "workitem types", "devops get", "devops create", "devops push", "devops pull", "devops sync", "check drift", "sync epic", "sync feature", or any request involving Azure DevOps work items.
|
DevOps Work Item
Manage Azure DevOps work items through subcommands. Supports CRUD operations, querying, and
bidirectional sync between the local agile framework (agile/epics/) and Azure DevOps.
MCP Compatibility
Minimum version: @wangkanai/devops-mcp@1.6.0
v1.6.0 fixed critical iteration path handling bugs (GitHub issue #68):
- Case-insensitive project name matching in
normalizeIterationPath()
- Removed forced
\Iteration\ intermediate node insertion
- Graceful failure when iteration path normalization fails
Iteration Path Format
Always use uppercase project name, no intermediate classification node:
GSB\Sprint 012 ← correct
gsb\Sprint 012 ← now works (v1.6.0+), but prefer uppercase
GSB\Iteration\Sprint 012 ← WRONG, will produce TF401347 errors
When inheriting iteration paths from queried work items (e.g., from a parent initiative),
use the returned value as-is — DevOps returns correctly formatted paths.
Error Recovery: TF401347
If create-work-item or update-work-item fails with TF401347: Invalid tree name:
- Omit
iterationPath from the call and retry — the item will be created without a sprint assignment
- After creation, set the iteration path via a separate
update-work-item call with the fields parameter:
{ "id": <created_id>, "fields": { "System.IterationPath": "GSB\\Sprint 012" } }
- If that also fails, query existing work items for valid iteration paths and use one verbatim
Token Efficiency Rules
These rules prevent bloated MCP responses that fill context:
-
Never bulk-fetch work items for push/update operations.
The shell scripts (parse-epic.sh, parse-feature.sh) provide all metadata locally.
Call update-work-item or create-work-item directly — no pre-fetch needed.
-
When you need remote state (pull/sync only), use fields parameter to limit response:
mcp__devops__get-work-items
ids: [5549, 5550, ...]
fields: ["System.Id", "System.State", "System.Title"]
This returns ~90% fewer tokens than fetching without fields.
WIQL does NOT filter fields — the fields parameter on get-work-items is the only way.
-
Process items sequentially, not in bulk batches.
One update-work-item call per item is cheaper than one get-work-items call for all items.
-
Only read reference files when the subcommand requires them (see References table below).
Invocation
/devops:workitem <subcommand> [arguments]
Subcommand Routing
| Subcommand | Arguments | Purpose |
|---|
get | <id> | Get work item by ID with full details |
create | <type> <title> [options] | Create new work item |
update | <id> [options] | Update existing work item |
search | <query> | Search work items by criteria |
list | [filters] | List work items by query filters |
types | | List all work item types in the project |
sprints | | List all iterations (sprints) |
areas | | List all area paths |
tags | | List all tags |
push | <epic-number | feature-id> | Push local artifacts to Azure DevOps |
State Mapping: Use scripts/map-state.sh to convert agile status to Azure DevOps state before updating work items:
DEVOPS_STATE=$(scripts/map-state.sh "$STATUS" "Epic")
For reverse mapping (pull from DevOps to local), use scripts/map-state-reverse.sh:
AGILE_STATUS=$(scripts/map-state-reverse.sh "$DEVOPS_STATE" "Epic")
| pull | <epic-number \| feature-id> | Pull DevOps state to local artifacts |
| sync | <epic-number \| feature-id> | Bidirectional sync (latest update wins) |
If no subcommand is provided, show the routing table and ask the user what they want to do.
Project Constants
| Constant | Value |
|---|
| Organization URL | https://dev.azure.com/senseinfo |
| Project | GSB |
| Assigned To | sarinwangkanai@outlook.com |
| Tags | agile-synced |
Subcommand: get
Usage: /devops:workitem get <id>
Retrieve a single work item. Use mcp__devops__get-work-items with ids: [<id>].
Present as: Work Item #<id> with Type, Title, State, Assigned, Iteration, Area, Parent, Tags, Risk, Priority, Effort.
Subcommand: create
Usage: /devops:workitem create <type> <title> [--parent <id>] [--state <state>] [--area <path>] [--sprint <path>] [--description <text>]
Read references/field-mapping.md for field names. Use mcp__devops__create-work-item.
Always set assignedTo: "sarinwangkanai@outlook.com" and tags: "agile-synced".
If --sprint not provided, query the most recent iteration from existing work items.
Subcommand: update
Usage: /devops:workitem update <id> [--title <t>] [--state <s>] [--description <d>] [--fields <json>]
Use mcp__devops__update-work-item. Only specified fields are changed.
For state transitions, read references/field-mapping.md for valid transitions.
Subcommand: search
Usage: /devops:workitem search <query> [--type <type>] [--state <state>] [--area <path>] [--sprint <path>] [--parent <id>] [--tags <tags>]
Build a WIQL query from provided filters using mcp__devops__get-work-items with wiql parameter.
Present results as a table: | ID | Type | Title | State | Area | Sprint |
Subcommand: list
Usage: /devops:workitem list [--type <t>] [--state <s>] [--area <p>] [--sprint <p>] [--parent <id>] [--recent <n>]
Same as search but without a text query. Defaults to items changed in the last 30 days, limit 50.
Subcommands: types, sprints, areas, tags
Query work items via WIQL, extract and deduplicate the relevant field. Read references/query-helpers.md for WIQL patterns.
Subcommand: push
Usage: /devops:workitem push <epic-number | feature-id>
Push local artifacts to Azure DevOps. Do NOT pre-fetch work items.
Argument resolution: 127 → entire epic. 127-01 → single feature.
Procedure:
-
Run scripts/parse-epic.sh <epic.md> to get epic metadata + feature list as JSON
-
Run scripts/find-unsynced.sh <epic-num> to identify which items need creating vs updating
-
Process in hierarchy order: Epic → Features → User Stories
For each artifact, branch on whether it has a DevOps ID:
Has DevOps ID → Update directly (no fetch needed):
mcp__devops__update-work-item
id: <devops_id>
title: "<parsed title>"
state: "<mapped state>" # Use field-mapping.md state mapping
No DevOps ID → Create (read references/push-create.md for full workflow):
mcp__devops__create-work-item
type: "<Epic|Feature|User Story>"
title: "<parsed title>"
parent: <parent_devops_id>
assignedTo: "sarinwangkanai@outlook.com"
tags: "agile-synced"
-
Backfill DevOps IDs into local artifacts:
- Epic/Feature header:
scripts/backfill-id.sh <file> <id>
- User Story:
scripts/backfill-id.sh <spec.md> <id> <story_number>
story_number is the digit from U{n} (e.g., 1 for U1, 2 for U2)
- The script inserts/updates
**DevOps ID**: <id> in the story's metadata block
-
Git commit the ID backfills
Three-tier hierarchy: Push creates work items in dependency order so parent IDs are available:
| Tier | Type | Parent | Backfill Target |
|---|
| 1 | Epic | Initiative (if any) | epic.md header |
| 2 | Feature | Epic DevOps ID | spec.md header |
| 3 | User Story | Feature DevOps ID | spec.md story section |
User Story extraction: Parse each feature's spec.md for ### NNN-XX-U{n} - Title headers.
Create one User Story work item per header, parented to the feature's DevOps ID.
Summary output:
| Type | Agile ID | DevOps ID | Title | Action |
|------------|------------|-----------|--------------------------|---------|
| Epic | 127 | 5549 | Epic Name | Updated |
| Feature | 127-01 | 5550 | Feature Name | Created |
| User Story | 127-01-U1 | 5551 | Story Title | Created |
Subcommand: pull
Usage: /devops:workitem pull <epic-number | feature-id>
Pull state updates from Azure DevOps. Uses fields parameter to limit response size.
Procedure:
- Run
scripts/collect-devops-ids.sh <epic-num> to get all DevOps IDs (Epic + Feature + User Story)
- Epic ID: from
epic.md header **DevOps ID**:
- Feature IDs: from each
spec.md header **DevOps ID**:
- User Story IDs: from each
spec.md story section **DevOps ID**:
- Query states with minimal fields:
mcp__devops__get-work-items
ids: [id1, id2, ...]
fields: ["System.Id", "System.State", "System.Title", "System.WorkItemType"]
- Compare DevOps state to local status (use
references/field-mapping.md bidirectional mapping)
- Epic/Feature: compare against artifact
**Status**: field
- User Story: compare against parent feature status (stories inherit feature status)
- Update local artifacts where DevOps state is more advanced:
scripts/update-artifact-state.sh
- Git commit state updates
Output: drift report showing what changed (all three tiers).
Subcommand: sync
Usage: /devops:workitem sync <epic-number | feature-id>
Bidirectional sync — most recently updated side wins.
Procedure:
- Run
scripts/collect-state-pairs.sh <epic-num> to get local statuses + DevOps IDs (all three tiers)
- Query DevOps with minimal fields:
mcp__devops__get-work-items
ids: [id1, id2, ...]
fields: ["System.Id", "System.State", "System.Title", "System.WorkItemType", "System.ChangedDate"]
- Compare local file mtime vs DevOps ChangedDate for each tier:
- Epic/Feature: compare
**Status**: field vs DevOps state
- User Story: compare parent feature status vs DevOps state
- Local newer →
mcp__devops__update-work-item
- DevOps newer →
scripts/update-artifact-state.sh
- Same state → skip
- Git commit any local changes
References
Read these files only when the subcommand requires them:
| File | When to Read |
|---|
references/field-mapping.md | create, update, push, pull, sync — for field names, state transitions, risk/priority mapping |
references/push-create.md | push — only when creating NEW items (no DevOps ID) |
references/template-changes.md | When artifact templates need **DevOps ID** fields |
references/query-helpers.md | types, sprints, areas, tags — for WIQL patterns |
Scripts
| Script | Purpose | Used By |
|---|
scripts/parse-epic.sh <epic.md> | Extract epic metadata as JSON | push |
scripts/parse-feature.sh <spec.md> | Extract feature metadata as JSON | push |
scripts/find-unsynced.sh [epic-num] | Find artifacts without DevOps IDs | push |
scripts/backfill-id.sh <file> <id> [story] | Write DevOps ID into artifact metadata | push, create |
scripts/collect-devops-ids.sh <epic-num> | Collect all DevOps IDs from an epic | pull, sync |
scripts/collect-state-pairs.sh <epic-num> | Collect statuses paired with DevOps IDs | sync |
scripts/update-artifact-state.sh <file> <status> | Update artifact status field | pull, sync |
scripts/update-epic-table.sh | Update epic breakdown table | push |
scripts/map-state.sh <status> [type] | Map agile status to DevOps state | push, update |
scripts/map-state-reverse.sh <state> [type] | Map DevOps state to agile status | pull, sync |