You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sub-issue of #138. Hardened spec — do not re-derive decisions. Sibling of #136 (work-item list), #203 (work-item create), #238 (work-item show), #269 (work-item delete).
Command Description
Update one or more fields of an existing Azure Boards work item by ID. Mirrors az boards work-item update. The command builds a JSON Patch document from the supplied flags and sends it to the server.
The REST surface is Work Items - Update (REST 7.1):
Body is a JSON Patch document (array of {op, path, value} ops). Response is the full updated WorkItem (id, rev, fields, _links, url, optional relations, commentVersionRef).
System.Description is an HTML field — Markdown source is accepted and rendered on the web form. The same description input modes used by #203 (create) are supported: inline (--description), file (--description-file, repeatable, - for stdin), and editor (--description-editor). The shared helper internal/cmd/boards/workitem/shared/description.go (introduced in #203) handles all three sources. Mirrors the AzDO Extension's update_work_item (azure-devops/azext_devops/dev/boards/work_item.py:106-170) and the AzDO MCP Server's update_work_item tool (microsoft/azure-devops-mcp/src/tools/work-items.ts:432-481).
Locked Decisions
#
Decision
Rationale
1
Org-scoped target.Use: "update [ORGANIZATION/]ID". The work item ID is unique within the organization.
Matches az boards work-item update (no project positional) and internal/cmd/boards/workitem/show.
2
Aliases: []string{"u"}.
Per AGENTS.md update command convention.
3
cobra.ExactArgs(1).
One positional target: the work item ID.
4
Parse the single positional with util.ParseTargetWithDefaultOrganization(ctx, args[0]). scope.Targets[0] is the ID string.
Existing helper handles 1- and 2-segment forms. Symmetric with #269 (delete).
5
No --project flag. PATCH URL is org-scoped (PATCH /_apis/wit/workitems/{id}); the Python never passes a project to client.update_work_item. The Go SDK's UpdateWorkItemArgs.Project is *string (optional) — pass nil.
Matches az boards work-item update exactly.
6
ID must parse as a positive integer. If strconv.Atoi(scope.Targets[0]) fails or returns <= 0, return util.FlagErrorf("work item ID must be a positive integer; got %q", scope.Targets[0]) and do not call the SDK.
Defends against typo'd IDs without a wasted round-trip. Symmetric with #269 Decision 14.
7
No mutex check on flags. The Python does not enforce a "at least one flag" rule — it builds whatever patch ops it can and sends them. The server rejects an empty patch with a 4xx. We surface that via util.FlagErrorWrap.
Matches az ergonomics. Avoids duplicating the server's validation.
8
--assigned-to passes the user-provided value as a plain string into /fields/System.AssignedTo. Same string-pass-through as #203 Decision 1. No identity resolution on the client.
Azure DevOps resolves the string server-side.
9
--fields parses as Ref.Name=value, split on the first= only. Reject if no = is present.
No tag-joining helper. The Python's update_work_item does not expose a --tag flag (only create does). Do not add one.
Matches az ergonomics — az boards work-item update has no --tag.
11
Canonical op order in the patch doc is fixed (see Code Skeleton §1). Tests assert this order.
Predictability for tests and for downstream debugging. Mirrors #203 Decision 4.
12
No new helpers beyond the shared description helper (Decision 23). Inline the patch-doc append using the webapi.JsonPatchOperation pattern from security/group/update/update.go:103-124.
Mandate: minimal code.
13
Identity helper reuse: do not import resolveAssignedToFilter from list.go. Use a plain string.
Mock AddComment negative assertion is mandatory. The TestRunUpdate_DiscussionTriggersAddComment test must include a literal wit.EXPECT().AddComment(gomock.Any(), gomock.Any()).Times(0) when --discussion is not set.
Default output: table row with columns ID, TYPE, STATE, TITLE, ASSIGNED TO, AREA, ITERATION — same shape as #203 (create). Lets the user verify the new state, title, and assignment.
Mirrors #203 table output. The data is useful; one-line message would hide it.
No go mod tidy / go mod vendor / scripts/generate_mocks.sh work.
23
The description accepts three input modes (Decisions 24-26 cover the rules): inline via --description, file via --description-file (repeatable, - reads from stdin), or interactive editor via --description-editor. Reuse internal/cmd/boards/workitem/shared/description.go (introduced in #203) unchanged — this command does not introduce a new helper.
Symmetric with #203. The shared helper is the single source of truth for description resolution.
24
--description-file <path> is repeatable. Multiple invocations concatenate with \n. The special token - reads from os.Stdin.
Mirrors #203 Decision 12. Lets the user reassemble descriptions from existing Markdown files.
25
--description-editor opens $VISUAL (preferred) or $EDITOR, falling back to vi on POSIX / notepad on Windows. A .md temp file is pre-populated with a header comment. Lines starting with # are stripped on read-back. Empty result → error.
Mirrors #203 Decision 13. The shared helper handles all of this; the update command just exposes the flag.
26
Description source precedence: editor > file > inline (most explicit wins). When the user supplies multiple sources, a warning to stderr names the source that was selected. The command does not error on multiple sources — it picks the highest-priority one and warns.
Mirrors #203 Decision 14. Both commands behave identically.
27
No Markdown↔HTML conversion on the client. The azdo CLI passes the description text through to the server's /fields/System.Description op unchanged. The Azure DevOps server renders Markdown.
Mirrors #203 Decision 16. The AzDO MCP Server's encodeFormattedValue is only relevant for fields that explicitly take a format parameter.
Command Signature
varupdateCmd=&cobra.Command{
Use: "update [ORGANIZATION/]ID",
Aliases: []string{"u"},
Short: "Update a work item.",
Long: heredoc.Doc(` Update one or more fields of an existing work item. The work item is identified by ID. Build a JSON Patch document from the supplied flags and send it to the server. At least one field flag is required. `),
Args: cobra.ExactArgs(1),
RunE: func(cmd*cobra.Command, args []string) error {
returnrunUpdate(cmd.Context(), opts, args[0])
},
}
Flags
Flag
Maps to
Notes
--title
/fields/System.Title
--description
/fields/System.Description
HTML/Markdown. Lower-priority than --description-file and --description-editor (Decision 26).
--description-file (repeatable)
/fields/System.Description
Read description from <path>. Repeatable; multiple files are concatenated with \n. The special token - reads from stdin. Higher-priority than --description (Decision 26).
--description-editor
/fields/System.Description
Open $VISUAL/$EDITOR (fallback vi/notepad) with a .md temp file. Pre-populated with a header comment. Lines starting with # are stripped. Highest-priority (Decision 26).
--assigned-to
/fields/System.AssignedTo
String pass-through (Decision 8)
--state
/fields/System.State
New state name
--area
/fields/System.AreaPath
--iteration
/fields/System.IterationPath
--reason
/fields/System.Reason
--fields (repeatable Ref.Name=value)
raw /fields/Ref.Name
Split on first = (Decision 9)
--discussion
comment via wit.AddComment
Side-effect — appends a comment to the work item, not a field patch (mirrors #203)
--bypass-rules
UpdateWorkItemArgs.BypassRules
--suppress-notifications
UpdateWorkItemArgs.SuppressNotifications
--validate-only
UpdateWorkItemArgs.ValidateOnly
--expand (enum)
UpdateWorkItemArgs.Expand
None/Relations/Fields/Links/All
--open
open returned URL in default browser
Mirrors az
--organization
*Path.Organization
Default org from config
Also add util.AddJSONFlags(cmd, &opts.exporter, []string{"id", "rev", "fields", "url", "_links", "relations", "commentVersionRef"}) (every JSON-tagged field of the updated *workitemtracking.WorkItem).
Code Skeleton (canonical, copy verbatim)
§1. updateOptions struct and canonical patch-doc order
The vendored SDK enforces args.Document != nil and args.Id != nil on UpdateWorkItem — will return ArgumentNilError if missing. No extra validation needed in our wrapper beyond the positive-integer check (Decision 6).
Reference Existing Patterns
internal/cmd/boards/workitem/create (feat: Implement azdo boards work-item create command #203) — primary reference. Mirrors the JSON Patch construction pattern, the raw *WorkItem JSON output, the shared/fields.go helpers, the progress indicator lifecycle, the webapi.JsonPatchOperation append pattern, and the AddComment discussion side-effect.
internal/cmd/boards/workitem/shared/description.go (introduced in feat: Implement azdo boards work-item create command #203) — single source of truth for description resolution. Reused unchanged by this command. Covers precedence, file reading (UTF-8 validation, 1 MB size cap, binary detection), editor invocation ($VISUAL/$EDITOR fallback), and header-comment stripping.
internal/cmd/security/group/update/update.go:103-124 — secondary reference for the 4-line patch-doc append pattern.
internal/cmd/boards/workitem/list/list.go — reference for the table output and field helpers.
internal/cmd/boards/workitem/show — sibling for the org-scoped ParseTargetWithDefaultOrganization usage.
Reference implementations (for UX parity only — azdo is the implementation target):
AzDO Extension azure-devops/azext_devops/dev/boards/work_item.py:106-170 - update_work_item(id, title, description, ...) confirms the description parameter is a plain string. The AzDO Extension does not have editor or file-import support; this is new in azdo.
AzDO MCP Server microsoft/azure-devops-mcp/src/tools/work-items.ts:432-481 - update_work_item MCP tool. Confirms the server's System.Description field accepts Markdown directly; no client-side conversion is needed.
TDD Test Plan
Test name
Scenario
Expected
TestNewCmd_update
Inspect the command struct.
Use == "update [ORGANIZATION/]ID", Aliases == ["u"], Args == cobra.ExactArgs(1).
Test_runUpdate_minimalTitle
Only --title set. Mock UpdateWorkItem returns a WorkItem.
Captured args.Document has exactly one add op on /fields/System.Title. Table renders with the new title.
Test_runUpdate_allOptionalFields_canonicalOrder
Every optional flag set.
Captured patch doc has exactly the 8 op positions in the order listed in Code Skeleton §1.
args.BypassRules and args.SuppressNotifications are &true. Stderr warning emitted.
Test_runUpdate_validateOnly
--validate-only set.
args.ValidateOnly is &true; response is still rendered.
Test_runUpdate_expand
--expand=All set.
args.Expand points to workitemtracking.WorkItemExpand("All").
Test_runUpdate_invalidID
Positional is "abc".
Returns util.FlagErrorf with "work item ID must be a positive integer; got \"abc\"". SDK is not called.
Test_runUpdate_zeroID
Positional is "0".
Returns util.FlagErrorf. SDK is not called.
Test_runUpdate_negativeID
Positional is "-5".
Returns util.FlagErrorf. SDK is not called.
Test_runUpdate_orgScopeOnly
Positional is "1234" (no org).
args.Id == ptr(1234), scope.Organization is the default.
Test_runUpdate_explicitOrg
Positional is "myorg/1234".
args.Id == ptr(1234), scope.Organization is "myorg".
Test_runUpdate_APIError
Mock returns errors.New("boom").
Returns wrapped error including the ID.
Test_runUpdate_success_JSON
--json flag set.
Exporter receives the raw *WorkItem (assert res.Id == ptr(1234)).
Test_runUpdate_tableOutput
Default output, mock returns a work item.
Table has 1 row with the 7 columns populated from res.Fields.
Test_runUpdate_fieldsParseSplitOnFirstEquals
--fields "Foo.Bar=key=value".
Parses as ref="Foo.Bar", value="key=value".
Test_runUpdate_emptyPatchDoc
No field flags set (only --bypass-rules for example).
Captured args.Document is a non-nil pointer to an empty slice. Server returns 4xx; we surface via util.FlagErrorWrap.
Test_runUpdate_openBrowserFlag
--open is a no-op in tests (DI not used).
Capture-and-discard.
Test_runUpdate_missingID
User runs azdo boards work-item update with no args.
Cobra args validation returns the standard "accepts 1 arg(s)" error.
Description tests (new — Decisions 23-27):
Test_runUpdate_DescriptionFromInline - --description "text". Captured args.Document has /fields/System.Description op with value "text".
Test_runUpdate_DescriptionFromSingleFile - --description-file ./foo.md (test creates a temp file). Captured op has the file's content.
Test_runUpdate_DescriptionFromStdin - --description-file - with ios.In set via iostreams.System().SetIn(strings.NewReader("stdin content")). Captured op has "stdin content".
Test_runUpdate_DescriptionFromMultipleFiles_Concatenated - --description-file a.md --description-file b.md. Captured op has a + "\n" + b.
Test_runUpdate_DescriptionFileNotFound - --description-file /nonexistent. Returns util.FlagErrorf with the path. SDK is not called.
Test_runUpdate_DescriptionFileBinary - temp file with null byte in first 8KB. Returns util.FlagErrorf with "appears to be binary".
Test_runUpdate_DescriptionFileNotUTF8 - temp file with invalid UTF-8 sequence. Returns util.FlagErrorf with "not valid UTF-8".
Test_runUpdate_DescriptionEditor - inject shared.execEditorCommand = fakeEditor("written by editor"). Captured op has "written by editor".
Test_runUpdate_DescriptionEditorStripsCommentLines - inject fakeEditor("# comment\n# also comment\n# my notes\nactual content\n"). Captured op has "actual content".
Test_runUpdate_DescriptionEditorEmptyAborts - inject fakeEditor(""). Returns util.FlagErrorf with "editor produced empty description".
Test_runUpdate_DescriptionPrecedenceEditorOverFile - both --description-editor and --description-file set. Editor wins; warning to stderr contains "takes precedence over --description-file".
Test_runUpdate_DescriptionPrecedenceEditorOverInline - both --description-editor and --description set. Editor wins; warning contains "takes precedence over --description".
Test_runUpdate_DescriptionPrecedenceFileOverInline - both --description-file and --description set. File wins; warning contains "takes precedence over --description".
Test_runUpdate_DescriptionAbsent_OmitsPatchOp - no description flags set. Captured args.Document has no/fields/System.Description op.
Sub-issue of #138. Hardened spec — do not re-derive decisions. Sibling of #136 (
work-item list), #203 (work-item create), #238 (work-item show), #269 (work-item delete).Command Description
Update one or more fields of an existing Azure Boards work item by ID. Mirrors
az boards work-item update. The command builds a JSON Patch document from the supplied flags and sends it to the server.The REST surface is Work Items - Update (REST 7.1):
Body is a JSON Patch document (array of
{op, path, value}ops). Response is the full updatedWorkItem(id,rev,fields,_links,url, optionalrelations,commentVersionRef).System.Descriptionis an HTML field — Markdown source is accepted and rendered on the web form. The same description input modes used by #203 (create) are supported: inline (--description), file (--description-file, repeatable,-for stdin), and editor (--description-editor). The shared helperinternal/cmd/boards/workitem/shared/description.go(introduced in #203) handles all three sources. Mirrors the AzDO Extension'supdate_work_item(azure-devops/azext_devops/dev/boards/work_item.py:106-170) and the AzDO MCP Server'supdate_work_itemtool (microsoft/azure-devops-mcp/src/tools/work-items.ts:432-481).Locked Decisions
Use: "update [ORGANIZATION/]ID". The work item ID is unique within the organization.az boards work-item update(no project positional) andinternal/cmd/boards/workitem/show.Aliases: []string{"u"}.cobra.ExactArgs(1).util.ParseTargetWithDefaultOrganization(ctx, args[0]).scope.Targets[0]is the ID string.delete).--projectflag. PATCH URL is org-scoped (PATCH /_apis/wit/workitems/{id}); the Python never passes a project toclient.update_work_item. The Go SDK'sUpdateWorkItemArgs.Projectis*string(optional) — passnil.az boards work-item updateexactly.strconv.Atoi(scope.Targets[0])fails or returns<= 0, returnutil.FlagErrorf("work item ID must be a positive integer; got %q", scope.Targets[0])and do not call the SDK.util.FlagErrorWrap.azergonomics. Avoids duplicating the server's validation.--assigned-topasses the user-provided value as a plain string into/fields/System.AssignedTo. Same string-pass-through as #203 Decision 1. No identity resolution on the client.--fieldsparses asRef.Name=value, split on the first=only. Reject if no=is present.update_work_itemdoes not expose a--tagflag (only create does). Do not add one.azergonomics —az boards work-item updatehas no--tag.webapi.JsonPatchOperationpattern fromsecurity/group/update/update.go:103-124.resolveAssignedToFilterfromlist.go. Use a plain string.WorkItemtoopts.exporter.Write. Do not introduce a view struct.--bypass-rulesor--suppress-notificationsis set.404is surfaced viautil.FlagErrorWrap.AddCommentnegative assertion is mandatory. TheTestRunUpdate_DiscussionTriggersAddCommenttest must include a literalwit.EXPECT().AddComment(gomock.Any(), gomock.Any()).Times(0)when--discussionis not set.ID, TYPE, STATE, TITLE, ASSIGNED TO, AREA, ITERATION— same shape as #203 (create). Lets the user verify the new state, title, and assignment.workitemtracking.Client.UpdateWorkItem.vendor/.../v7/workitemtracking/client.go:3602(impl) and:205(interface).internal/mocks/workitemtracking_client_mock.go:1358-1371already mocksUpdateWorkItem.AddCommentmock already exists.go mod tidy/go mod vendor/scripts/generate_mocks.shwork.--description, file via--description-file(repeatable,-reads from stdin), or interactive editor via--description-editor. Reuseinternal/cmd/boards/workitem/shared/description.go(introduced in #203) unchanged — this command does not introduce a new helper.--description-file <path>is repeatable. Multiple invocations concatenate with\n. The special token-reads fromos.Stdin.--description-editoropens$VISUAL(preferred) or$EDITOR, falling back tovion POSIX /notepadon Windows. A.mdtemp file is pre-populated with a header comment. Lines starting with#are stripped on read-back. Empty result → error.azdoCLI passes the description text through to the server's/fields/System.Descriptionop unchanged. The Azure DevOps server renders Markdown.encodeFormattedValueis only relevant for fields that explicitly take aformatparameter.Command Signature
Flags
--title/fields/System.Title--description/fields/System.Description--description-fileand--description-editor(Decision 26).--description-file(repeatable)/fields/System.Description<path>. Repeatable; multiple files are concatenated with\n. The special token-reads from stdin. Higher-priority than--description(Decision 26).--description-editor/fields/System.Description$VISUAL/$EDITOR(fallbackvi/notepad) with a.mdtemp file. Pre-populated with a header comment. Lines starting with#are stripped. Highest-priority (Decision 26).--assigned-to/fields/System.AssignedTo--state/fields/System.State--area/fields/System.AreaPath--iteration/fields/System.IterationPath--reason/fields/System.Reason--fields(repeatableRef.Name=value)/fields/Ref.Name=(Decision 9)--discussionwit.AddComment--bypass-rulesUpdateWorkItemArgs.BypassRules--suppress-notificationsUpdateWorkItemArgs.SuppressNotifications--validate-onlyUpdateWorkItemArgs.ValidateOnly--expand(enum)UpdateWorkItemArgs.ExpandNone/Relations/Fields/Links/All--openaz--organization*Path.OrganizationAlso add
util.AddJSONFlags(cmd, &opts.exporter, []string{"id", "rev", "fields", "url", "_links", "relations", "commentVersionRef"})(every JSON-tagged field of the updated*workitemtracking.WorkItem).Code Skeleton (canonical, copy verbatim)
§1.
updateOptionsstruct and canonical patch-doc orderThe patch doc must append ops in this exact order. Tests assert order:
/fields/System.Title/fields/System.Description(only ifResolveDescriptionreturns non-empty)/fields/System.AssignedTo/fields/System.State/fields/System.AreaPath/fields/System.IterationPath/fields/System.Reason/fields/ops from--fields(in user-given order)/relations/-— Pythonupdatedoes not expose--link.)/fields/System.Tags— Decision 10.)§2. Patch-doc construction (4-line pattern, copy from
security/group/update/update.go:103-124)§3.
runUpdatesignature§4.
NewCmdshape (no surprises)§5.
runUpdateskeletonfieldStringandfieldIdentityDisplayare already ininternal/cmd/boards/workitem/shared/fields.go(promoted by #203). Reuse from there.JSON Output Contract
Pass the raw
*workitemtracking.WorkItemreturned byUpdateWorkItemtoopts.exporter.Write. No view struct.JSON fields exposed (matching SDK struct tags):
id,rev,fields,url,_links,relations,commentVersionRef.Table Output Contract
Use
ctx.Printer("list")with the same 7 columns andAddFieldsequence as #203. The table renders a single row containing the updated work item's data.Command Wiring
internal/cmd/boards/workitem/update/update.gowithpackage update, factoryfunc NewCmd(ctx util.CmdContext) *cobra.Command.import "github.com/tmeckel/azdo-cli/internal/cmd/boards/workitem/update"tointernal/cmd/boards/workitem/workitem.go.workitem.gowithcmd.AddCommand(update.NewCmd(ctx))and extend the group'sExampleblock.internal/cmd/boards/workitem/shared/fields.go(fieldString,fieldIdentityDisplay) promoted by feat: Implementazdo boards work-item createcommand #203.internal/cmd/boards/workitem/shared/description.go(introduced in feat: Implementazdo boards work-item createcommand #203) —shared.ResolveDescription,shared.ReadDescriptionFiles,shared.OpenEditor. No new shared file.API Surface
Vendored SDK call (from
vendor/.../v7/workitemtracking/client.go:3602):AddCommentcall (only when--discussionis set):The vendored SDK enforces
args.Document != nilandargs.Id != nilonUpdateWorkItem— will returnArgumentNilErrorif missing. No extra validation needed in our wrapper beyond the positive-integer check (Decision 6).Reference Existing Patterns
internal/cmd/boards/workitem/create(feat: Implementazdo boards work-item createcommand #203) — primary reference. Mirrors the JSON Patch construction pattern, the raw*WorkItemJSON output, theshared/fields.gohelpers, the progress indicator lifecycle, thewebapi.JsonPatchOperationappend pattern, and theAddCommentdiscussion side-effect.internal/cmd/boards/workitem/shared/description.go(introduced in feat: Implementazdo boards work-item createcommand #203) — single source of truth for description resolution. Reused unchanged by this command. Covers precedence, file reading (UTF-8 validation, 1 MB size cap, binary detection), editor invocation ($VISUAL/$EDITORfallback), and header-comment stripping.internal/cmd/security/group/update/update.go:103-124— secondary reference for the 4-line patch-doc append pattern.internal/cmd/boards/workitem/list/list.go— reference for the table output and field helpers.internal/cmd/boards/workitem/show— sibling for the org-scopedParseTargetWithDefaultOrganizationusage.Reference implementations (for UX parity only —
azdois the implementation target):azure-devops/azext_devops/dev/boards/work_item.py:106-170-update_work_item(id, title, description, ...)confirms thedescriptionparameter is a plain string. The AzDO Extension does not have editor or file-import support; this is new inazdo.microsoft/azure-devops-mcp/src/tools/work-items.ts:432-481-update_work_itemMCP tool. Confirms the server'sSystem.Descriptionfield accepts Markdown directly; no client-side conversion is needed.TDD Test Plan
TestNewCmd_updateUse == "update [ORGANIZATION/]ID",Aliases == ["u"],Args == cobra.ExactArgs(1).Test_runUpdate_minimalTitle--titleset. MockUpdateWorkItemreturns aWorkItem.args.Documenthas exactly oneaddop on/fields/System.Title. Table renders with the new title.Test_runUpdate_allOptionalFields_canonicalOrderTest_runUpdate_customFields--fields "Foo.Bar=value" --fields "Baz.Qux=other"./fields/ops appended at positions 8+ in user-given order.Test_runUpdate_discussionTriggersAddComment--discussionset.wit.AddCommentis called once with the discussion text andWorkItemIdmatching the response'sId.Test_runUpdate_noDiscussion--discussionnot set.wit.EXPECT().AddComment(gomock.Any(), gomock.Any()).Times(0).Test_runUpdate_bypassRulesAndSuppressNotificationsargs.BypassRulesandargs.SuppressNotificationsare&true. Stderr warning emitted.Test_runUpdate_validateOnly--validate-onlyset.args.ValidateOnlyis&true; response is still rendered.Test_runUpdate_expand--expand=Allset.args.Expandpoints toworkitemtracking.WorkItemExpand("All").Test_runUpdate_invalidID"abc".util.FlagErrorfwith"work item ID must be a positive integer; got \"abc\"". SDK is not called.Test_runUpdate_zeroID"0".util.FlagErrorf. SDK is not called.Test_runUpdate_negativeID"-5".util.FlagErrorf. SDK is not called.Test_runUpdate_orgScopeOnly"1234"(no org).args.Id == ptr(1234), scope.Organization is the default.Test_runUpdate_explicitOrg"myorg/1234".args.Id == ptr(1234), scope.Organization is"myorg".Test_runUpdate_APIErrorerrors.New("boom").Test_runUpdate_success_JSON--jsonflag set.*WorkItem(assertres.Id == ptr(1234)).Test_runUpdate_tableOutputres.Fields.Test_runUpdate_fieldsParseSplitOnFirstEquals--fields "Foo.Bar=key=value".ref="Foo.Bar",value="key=value".Test_runUpdate_emptyPatchDoc--bypass-rulesfor example).args.Documentis a non-nil pointer to an empty slice. Server returns 4xx; we surface viautil.FlagErrorWrap.Test_runUpdate_openBrowserFlag--openis a no-op in tests (DI not used).Test_runUpdate_missingIDazdo boards work-item updatewith no args.Description tests (new — Decisions 23-27):
Test_runUpdate_DescriptionFromInline---description "text". Capturedargs.Documenthas/fields/System.Descriptionop with value"text".Test_runUpdate_DescriptionFromSingleFile---description-file ./foo.md(test creates a temp file). Captured op has the file's content.Test_runUpdate_DescriptionFromStdin---description-file -withios.Inset viaiostreams.System().SetIn(strings.NewReader("stdin content")). Captured op has"stdin content".Test_runUpdate_DescriptionFromMultipleFiles_Concatenated---description-file a.md --description-file b.md. Captured op hasa + "\n" + b.Test_runUpdate_DescriptionFileNotFound---description-file /nonexistent. Returnsutil.FlagErrorfwith the path. SDK is not called.Test_runUpdate_DescriptionFileTooLarge- temp file > 1 MB. Returnsutil.FlagErrorfwith "exceeds 1 MB".Test_runUpdate_DescriptionFileBinary- temp file with null byte in first 8KB. Returnsutil.FlagErrorfwith "appears to be binary".Test_runUpdate_DescriptionFileNotUTF8- temp file with invalid UTF-8 sequence. Returnsutil.FlagErrorfwith "not valid UTF-8".Test_runUpdate_DescriptionEditor- injectshared.execEditorCommand = fakeEditor("written by editor"). Captured op has"written by editor".Test_runUpdate_DescriptionEditorStripsCommentLines- injectfakeEditor("# comment\n# also comment\n# my notes\nactual content\n"). Captured op has"actual content".Test_runUpdate_DescriptionEditorEmptyAborts- injectfakeEditor(""). Returnsutil.FlagErrorfwith "editor produced empty description".Test_runUpdate_DescriptionEditorNonZeroExit- inject editor that returns exit code 1. Returns wrapped error.Test_runUpdate_DescriptionPrecedenceEditorOverFile- both--description-editorand--description-fileset. Editor wins; warning to stderr contains "takes precedence over --description-file".Test_runUpdate_DescriptionPrecedenceEditorOverInline- both--description-editorand--descriptionset. Editor wins; warning contains "takes precedence over --description".Test_runUpdate_DescriptionPrecedenceFileOverInline- both--description-fileand--descriptionset. File wins; warning contains "takes precedence over --description".Test_runUpdate_DescriptionAbsent_OmitsPatchOp- no description flags set. Capturedargs.Documenthas no/fields/System.Descriptionop.References
vendor/.../v7/workitemtracking/client.go:3602(UpdateWorkItem),:3633(UpdateWorkItemArgs),:267(AddWorkItemComment).vendor/.../v7/workitemtracking/models.go(WorkItemat ~L820,Commentat ~L1080).internal/mocks/workitemtracking_client_mock.go:1358-1371(UpdateWorkItem),AddWorkItemComment(already generated).internal/azdo/factory.go:149—ClientFactory().WorkItemTracking(ctx, organization).72c7ddf8-2cdc-4f60-90cd-ab71c14a399b, API version7.1-preview.3).azdo boards work-itemcommand group #138.azdo boards work-item listcommand #136 (list), feat: Implementazdo boards work-item createcommand #203 (create, includes the fullshared/description.gospec), feat: Implementazdo boards work-item showcommand #238 (show), feat: Implementazdo boards work-item deletecommand #269 (delete).internal/cmd/boards/workitem/create(feat: Implementazdo boards work-item createcommand #203) — JSON Patch construction, raw SDK type toopts.exporter.Write, discussion side-effect, shared description helper.