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 #141. Sibling of #143 (list), #204 (create), #206 (delete), #208 (update). Hardened spec — do not re-derive decisions. Mirrors iteration project show (sibling under #142) with StructureGroup=Areas; areas have no startDate/finishDate attributes.
Command Description
Display the details of a single area path node in a project's area tree. The user supplies a fully-qualified area path; the command fetches the matching WorkItemClassificationNode via the Classification Nodes REST 7.1 endpoint and renders it as a Go text template (the same engine used by azdo pr view).
GET https://dev.azure.com/{organization}/{project}/_apis/wit/classificationnodes/Areas/{nodePath}?$depth={depth}&api-version=7.1
Locked Decisions (do not re-derive)
#
Decision
Rationale
1
Use the vendored SDK workitemtracking.Client.GetClassificationNode (not raw HTTP). Mock already generated.
Consistent with the list and create siblings; the SDK is what area project list already uses.
2
The node is identified by --path, not a positional. URL-escaped by shared.BuildClassificationPath and passed to GetClassificationNodeArgs.Path.
--path is required. Empty / whitespace rejected with util.FlagErrorf.
Without a path the SDK would return the project root, which is the list command's job.
4
--depth defaults to 0 (just the node, no children). Valid range 0..10; mapped to GetClassificationNodeArgs.Depth.
show is for inspecting a single node. Default 0 keeps the response small.
5
Use the Go text template engine from internal/template/template.go with an //go:embed show.tpl file. Mirror internal/cmd/pr/view/view.go and view.tpl structure.
The user explicitly requested template rendering like pr/view.
6
Aliases: view, status. Primary name is show. cmd.Use: "show [ORGANIZATION/]PROJECT", cmd.Aliases: []string{"view", "status"}.
Mirrors the pr/view aliasing pattern but with show as the primary.
7
JSON output passes the raw SDK *WorkItemClassificationNode to opts.exporter.Write. No view struct.
--raw flag dumps the full SDK node with spew.Dump to stderr for debugging.
Mirrors pr/view --raw.
10
--include-children (default false) flag controls whether the template iterates over Children.
Children can be large; default to compact.
11
Attributes rendered as a key: value list under a attributes: heading. Areas typically have no attributes; when present (e.g. custom Budget), render them.
Areas are mostly attribute-less. Keep the block; rarely rendered.
12
No new SDK client, no new helper, no new package beyond internal/cmd/boards/area/project/show. Reuse shared.BuildClassificationPath and shared.NormalizeClassificationPath from #143/#204.
Mandate: minimal code.
13
Mock for GetClassificationNode is already generated at internal/mocks/workitemtracking_client_mock.go. Do not regenerate.
Verified; same mock serves all classification-node commands.
Command Signature
azdo boards area project show [ORGANIZATION/]PROJECT
Aliases: view, status
Positional parsing via util.ParseProjectScope(ctx, scopeArg). Errors wrapped with util.FlagErrorWrap.
--path is required; --depth defaults to 0.
Flags
Flag
Maps to
Notes
--path (required)
GetClassificationNodeArgs.Path
URL-escaped via shared.BuildClassificationPath(scope.Project, true, "Area", opts.path) (note: scope is "Area" singular, matching #143's convention)
Update internal/cmd/boards/area/project/project.go to add showcmd "github.com/tmeckel/azdo-cli/internal/cmd/boards/area/project/show" and cmd.AddCommand(showcmd.NewCmd(ctx)). Update the Example block.
Existing higher-level parents must already remain wired: boards → area → project → show.
API Surface
Reuse the already-vendored client. No new SDK clients required.
Phase 1 — RED (tests first). Mirror setupFakeDeps from internal/cmd/boards/workitem/list/list_test.go:765-844. Add show_test.go with the following table-driven / behaviour tests, all using t.Parallel() and gomock:
TestNewCmd_RegistersAsShowLeaf — asserts cmd.Name() == "show", cmd.Aliases contains view and status, cmd.Use starts with show [ORGANIZATION/]PROJECT.
TestRunShow_ClientFactoryError — stubs factory to return error; asserts wrapped error.
TestRunShow_SDKError — stubs SDK to return error; asserts wrapped error.
TestRunShow_OrganizationFromConfigDefault — when scopeArg is PROJECT (no org), asserts clientFact.WorkItemTracking(ctx, defaultOrg) is called with the configured default.
Phase 2 — GREEN (minimal implementation). Strict reuse rules:
No new helpers beyond the inline formatAttributeDate (~10 lines) if a timefmt template function isn't expressive enough.
Output split: JSON via opts.exporter.Write(ios, res) passing the raw SDK *WorkItemClassificationNode; template via template.New(...).ExecuteData(templateData{Node: res}).
Debug log at the point of the SDK call: organization, project, path, depth.
Target delta: show.go ≤ ~120 LOC, show.tpl ≤ ~45 LOC, show_test.go ≤ ~400 LOC (24 tests), parent project.go +3 LOC, docs/boards_area_project_show.md regenerated via make docs. No changes to list.go, create.go, delete.go, update.go, or area.go.
Tooling and Verification Checklist
Run gofmt / gofumpt on touched files
go test ./internal/cmd/boards/area/...
go test ./...
make lint
make docs
Reference Existing Patterns
internal/cmd/pr/view/view.go — primary template-engine reference (Primary: view.go:483-549; viewOptions struct at view.go:23-31; template embed at view.go:33-34).
internal/cmd/pr/view/view.tpl — primary template-file reference.
internal/cmd/boards/area/project/list/list.go — same BuildClassificationPath(project, true, "Area", ...) and TreeStructureGroupValues.Areas (reuse).
Sub-issue of #141. Sibling of #143 (
list), #204 (create), #206 (delete), #208 (update). Hardened spec — do not re-derive decisions. Mirrorsiteration project show(sibling under #142) withStructureGroup=Areas; areas have nostartDate/finishDateattributes.Command Description
Display the details of a single area path node in a project's area tree. The user supplies a fully-qualified area path; the command fetches the matching
WorkItemClassificationNodevia the Classification Nodes REST 7.1 endpoint and renders it as a Go text template (the same engine used byazdo pr view).Locked Decisions (do not re-derive)
workitemtracking.Client.GetClassificationNode(not raw HTTP). Mock already generated.listandcreatesiblings; the SDK is whatarea project listalready uses.--path, not a positional. URL-escaped byshared.BuildClassificationPathand passed toGetClassificationNodeArgs.Path.--pathflag in #143, #204, #208.--pathis required. Empty / whitespace rejected withutil.FlagErrorf.listcommand's job.--depthdefaults to0(just the node, no children). Valid range0..10; mapped toGetClassificationNodeArgs.Depth.showis for inspecting a single node. Default 0 keeps the response small.internal/template/template.gowith an//go:embed show.tplfile. Mirrorinternal/cmd/pr/view/view.goandview.tplstructure.pr/view.view,status. Primary name isshow.cmd.Use: "show [ORGANIZATION/]PROJECT",cmd.Aliases: []string{"view", "status"}.pr/viewaliasing pattern but withshowas the primary.*WorkItemClassificationNodetoopts.exporter.Write. No view struct.--rawflag dumps the full SDK node withspew.Dumpto stderr for debugging.pr/view --raw.--include-children(defaultfalse) flag controls whether the template iterates overChildren.Attributesrendered as akey: valuelist under aattributes:heading. Areas typically have no attributes; when present (e.g. customBudget), render them.internal/cmd/boards/area/project/show. Reuseshared.BuildClassificationPathandshared.NormalizeClassificationPathfrom #143/#204.GetClassificationNodeis already generated atinternal/mocks/workitemtracking_client_mock.go. Do not regenerate.Command Signature
view,statusutil.ParseProjectScope(ctx, scopeArg). Errors wrapped withutil.FlagErrorWrap.--pathis required;--depthdefaults to0.Flags
--path(required)GetClassificationNodeArgs.Pathshared.BuildClassificationPath(scope.Project, true, "Area", opts.path)(note: scope is"Area"singular, matching #143's convention)--depth(optional, default 0)GetClassificationNodeArgs.Depth--include-children(bool, default false)Childrenblock--raw(bool)spew.Dumpof full SDK node to stderr--json/--jq/--templateutil.AddJSONFlagsJSON Output Contract
Pass the raw
*workitemtracking.WorkItemClassificationNodereturned by the SDK (Decision 7).Template Output Contract (
show.tpl)The default template renders (see
internal/cmd/pr/view/view.tplfor the established pattern):No
startDate/finishDateblock (areas have no schedule attributes).Command Wiring
internal/cmd/boards/area/project/showshow.go—NewCmd(ctx util.CmdContext) *cobra.Command+showOptions+runShowshow.tpl— Go text templateshow_test.go— table-driven gomock testsinternal/cmd/boards/area/project/project.goto addshowcmd "github.com/tmeckel/azdo-cli/internal/cmd/boards/area/project/show"andcmd.AddCommand(showcmd.NewCmd(ctx)). Update theExampleblock.boards→area→project→show.API Surface
Reuse the already-vendored client. No new SDK clients required.
workitemtracking.Client.GetClassificationNode→ Classification Nodes - Get Classification Node (REST 7.1)workitemtracking.TreeStructureGroupValues.Areas="areas"internal/template.Templatewithbold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerenderMock for
GetClassificationNodeis already generated atinternal/mocks/workitemtracking_client_mock.go. No mock regeneration needed.Implementation Approach (TDD, reuse-first, minimal)
Phase 1 — RED (tests first). Mirror
setupFakeDepsfrominternal/cmd/boards/workitem/list/list_test.go:765-844. Addshow_test.gowith the following table-driven / behaviour tests, all usingt.Parallel()andgomock:TestNewCmd_RegistersAsShowLeaf— assertscmd.Name() == "show",cmd.Aliasescontainsviewandstatus,cmd.Usestarts withshow [ORGANIZATION/]PROJECT.TestNewCmd_PathFlagRequired— runscmd.SetArgs([]string{"Fabrikam"})+cmd.Execute(); asserts cobraMarkFlagRequirederror mentioningpath.TestRunShow_PathRequired_Empty— sets--path ""; assertsutil.FlagErrorfreturned.TestRunShow_PathRequired_Whitespace— sets--path " "; assertsutil.FlagErrorfreturned.TestRunShow_RootLevelCall— sets--path "Payments"; assertsargs.Path == "Payments",*args.StructureGroup == "areas",*args.Project == "Fabrikam",*args.Depth == 0.TestRunShow_PathNormalizationStripsProjectAndArea— sets--path "Fabrikam/Area/Payments/Web"; assertsargs.Path == "Payments/Web".TestRunShow_PathURLEscaping— sets--path "My Team/Sub Team"; assertsargs.Path == "My%20Team/Sub%20Team".TestRunShow_DepthFlag— sets--depth 2; asserts*args.Depth == 2.TestRunShow_DepthBounds— sets--depth 11; assertsutil.FlagErrorfwith depth bounds message.TestRunShow_StructureGroupIsAreas— asserts*args.StructureGroup == "areas".TestRunShow_TemplateOutput_BasicFields— mocks return*WorkItemClassificationNode{Id, Identifier, Name, Path, StructureType, HasChildren, Url}; asserts rendered output contains all field labels and values.TestRunShow_TemplateOutput_Hyperlink— asserts theurl:line uses ANSI hyperlink escape sequence.TestRunShow_TemplateOutput_AttributesBlock_Present— mocks returnAttributes{Department: "Engineering"}; asserts block rendered withDepartment: Engineering.TestRunShow_TemplateOutput_AttributesBlock_NilOrEmpty— mocks returnnilor empty attributes; asserts theattributes:block is omitted.TestRunShow_TemplateOutput_NoStartOrFinishDate— explicitly asserts the rendered output does not containstartDate:orfinishDate:(area-specific).TestRunShow_TemplateOutput_ChildrenIncluded— sets--include-children; mocks return node withChildren; asserts children block rendered.TestRunShow_TemplateOutput_ChildrenOmitted_Default— default flags; mocks return node withChildren; asserts children block omitted.TestRunShow_JSONOutput— sets--json; mocks return node; asserts JSON containsid,identifier,name,path,_links,hasChildren,url,structureType,attributes.TestRunShow_RawFlag— sets--raw; assertsspew.Dumpwas invoked.TestRunShow_ProjectScopeParsing— table-driven:[ORG/]PROJECT,PROJECT, invalid ("org/proj/extra"), empty.TestRunShow_InvalidProjectScope— assertsutil.FlagErrorWrapreturned.TestRunShow_ClientFactoryError— stubs factory to return error; asserts wrapped error.TestRunShow_SDKError— stubs SDK to return error; asserts wrapped error.TestRunShow_OrganizationFromConfigDefault— when scopeArg isPROJECT(no org), assertsclientFact.WorkItemTracking(ctx, defaultOrg)is called with the configured default.Phase 2 — GREEN (minimal implementation). Strict reuse rules:
formatAttributeDate(~10 lines) if atimefmttemplate function isn't expressive enough.util.ParseProjectScope,util.AddJSONFlags,util.FlagErrorf/FlagErrorWrap,util.ExactArgs,types.GetValue,types.ToPtr,ios.StartProgressIndicator/StopProgressIndicator,iostreams.Test,shared.BuildClassificationPath,shared.NormalizeClassificationPathas-is.internal/template.New(...).WithFuncs(...).Parse(show.tpl).ExecuteData(data)— exact same pattern asinternal/cmd/pr/view/view.go:483-549.ios.StartProgressIndicator()+defer ios.StopProgressIndicator(); callios.StopProgressIndicator()immediately before template execution.opts.exporter.Write(ios, res)passing the raw SDK*WorkItemClassificationNode; template viatemplate.New(...).ExecuteData(templateData{Node: res}).Target delta:
show.go≤ ~120 LOC,show.tpl≤ ~45 LOC,show_test.go≤ ~400 LOC (24 tests), parentproject.go+3 LOC,docs/boards_area_project_show.mdregenerated viamake docs. No changes tolist.go,create.go,delete.go,update.go, orarea.go.Tooling and Verification Checklist
gofmt/gofumpton touched filesgo test ./internal/cmd/boards/area/...go test ./...make lintmake docsReference Existing Patterns
internal/cmd/pr/view/view.go— primary template-engine reference (Primary:view.go:483-549;viewOptionsstruct atview.go:23-31; template embed atview.go:33-34).internal/cmd/pr/view/view.tpl— primary template-file reference.internal/cmd/boards/area/project/list/list.go— sameBuildClassificationPath(project, true, "Area", ...)andTreeStructureGroupValues.Areas(reuse).internal/cmd/boards/area/project/create/create.go(feat: Implementazdo boards area project createcommand #204) — closest sibling; mirror JSON/table split, raw-SDK JSON output, progress lifecycle.internal/cmd/boards/iteration/project/show/show.go(sibling under feat: Introduceazdo boards iteration projectcommand group #142) — copy structure exactly withStructureGroup=Areas.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDeps/stub*fixture; copy structure.internal/cmd/boards/shared/path.go—BuildClassificationPath+NormalizeClassificationPath(reuse, do not reimplement).internal/mocks/workitemtracking_client_mock.go— mock forGetClassificationNode(already generated, do not regenerate).internal/azdo/factory.go—ClientFactory().WorkItemTracking(...)accessor (reuse).internal/template/template.go— template engine + funcs (reuse, do not reimplement).References