Skip to content

feat(#44): add az-Show-AzDevOpsAreas and az-Show-AzDevOpsIterations#45

Closed
jdschleicher wants to merge 1 commit intomainfrom
feature/show-azdevops-areas-iterations
Closed

feat(#44): add az-Show-AzDevOpsAreas and az-Show-AzDevOpsIterations#45
jdschleicher wants to merge 1 commit intomainfrom
feature/show-azdevops-areas-iterations

Conversation

@jdschleicher
Copy link
Copy Markdown
Owner

Summary

Adds az-Show-AzDevOpsAreas and az-Show-AzDevOpsIterations so the area / iteration classification trees join az-Show-AzDevOpsTree in the keyboard-discoverable az-Show- family. Both functions delegate to a private Show-AzDevOpsClassification -Kind helper that does cache-first reads with a live az fallback (gated by Assert-AzDevOpsAuthOrAbort), and renders to Out-ConsoleGridView when available with an indented text-tree fallback.

Issue

Closes #44

Changes

New public functions (powcuts_by_cli/azdevops_workitems.ps1)

  • az-Show-AzDevOpsAreas — prints the area-path tree
  • az-Show-AzDevOpsIterations — prints the iteration-path tree (with StartDate / FinishDate columns from cached attributes)

New private helpers

  • Show-AzDevOpsClassification -Kind <Iteration|Area> — orchestrator; cache-first with live fallback, stale banner, dispatches to Show-AzDevOpsRows (grid) or Format-AzDevOpsClassificationNode (text fallback)
  • Get-AzDevOpsClassificationRows — depth-first flattener; emits one PSCustomObject per node with Depth / Name / Path (and StartDate / FinishDate for iterations as ISO yyyy-MM-dd); skips the synthetic \<Project>\<Kind> root
  • Format-AzDevOpsClassificationNode — text-fallback per-node formatter (<indent><name> for areas; <indent><name><tab><start> → <finish> for iterations when both dates present)

Documentation

  • README.md — adds bullets to the day-to-day shortcuts code block, extends the stale-banner sentence and Out-ConsoleGridView consumer list, mentions the two new commands in the long intro paragraph
  • docs/azure-devops-diagrams.md — Diagram 1 gains ShowAreas + ShowIters nodes wired to AreasJson / IterJson (cache-first) and AzBoards (dotted live-fallback); Diagram 9 gains a "Classification tree views" cluster with the three new private helpers

AC Adaptation Note

Issue #44's AC said "indented text output" with grid migration in Out of Scope ("defer to whenever az-Show-AzDevOpsTree migrates"). That deferral is now obsolete — PR #36 already migrated az-Show-AzDevOpsTree to grid-when-available + text-fallback. This PR follows the same shape so the az-Show- family stays uniform; the indented text-tree path is preserved as the fallback when Out-ConsoleGridView isn't installed.

Test Plan

  • pwsh -NoProfile -Command "[System.Management.Automation.Language.Parser]::ParseFile('powcuts_by_cli/azdevops_workitems.ps1', [ref]\$null, [ref]\$null) | Out-Null; 'PARSE OK'" — should print PARSE OK
  • In a fresh PowerShell terminal, dot-source $profile; type az-Show- + tab-tab — Tree, Areas, Iterations (and any other az-Show-* already shipped) appear together
  • Run az-Sync-AzDevOpsCache, then az-Show-AzDevOpsAreas — instant render from cache
  • az-Show-AzDevOpsIterations — same, with StartDate / FinishDate columns populated for sprint nodes that have dates
  • Remove-Item $HOME/.bashcuts-cache/azure-devops/iterations.json; az-Show-AzDevOpsIterations — fires live, prints the (run az-Sync-AzDevOpsCache to make this instant) hint, then renders
  • Without Microsoft.PowerShell.ConsoleGuiTools installed (or in an environment where Out-ConsoleGridView isn't on PATH), both commands fall back to Format-Table via Show-AzDevOpsRows

Generated by Claude Code

Two keyboard-discoverable Show- entry points for the area/iteration
classification trees. Closes the last gap in the az-Show- family - users
can now tab-tab on `az-Show-` and see Tree, Areas, Iterations together.

New public functions in powcuts_by_cli/azdevops_workitems.ps1:
  az-Show-AzDevOpsAreas       - prints the project's area-path tree
  az-Show-AzDevOpsIterations  - prints the iteration-path tree (with
                                start/finish dates per node)

Both are 3-line wrappers that delegate to a private
Show-AzDevOpsClassification -Kind helper (CLAUDE.md
extract-repeated-branches rule).

Internal helpers added alongside:
  Get-AzDevOpsClassificationRows  - depth-first flattener; emits one
                                    PSCustomObject per node with Depth,
                                    Name, Path. For -Kind 'Iteration',
                                    also surfaces StartDate / FinishDate
                                    as ISO yyyy-MM-dd strings from the
                                    cached attributes.
  Format-AzDevOpsClassificationNode - text-fallback per-node formatter:
                                      '<indent><name>' for areas;
                                      '<indent><name><tab><start> ->
                                      <finish>' for iterations when
                                      both dates are present.
  Show-AzDevOpsClassification     - orchestrator: cache-first via
                                    Read-AzDevOpsClassificationCache,
                                    on $null falls back to live
                                    Invoke-AzDevOpsClassificationLive
                                    (gated by Assert-AzDevOpsAuthOrAbort)
                                    and prints the
                                    `(run az-Sync-AzDevOpsCache to make
                                    this instant)` hint. Stale-banner on
                                    cache hits. Dispatches to
                                    Show-AzDevOpsRows for grid-when-
                                    available + Format-Table fallback,
                                    or per-node text print when grid +
                                    Format-Table unavailable.

Note: issue #44's AC said "indented text output" with grid migration
listed in Out of Scope. That deferral is now obsolete - PR #36 already
migrated az-Show-AzDevOpsTree to grid-when-available + text-fallback.
This implementation adopts the same pattern so the Show- family stays
uniform.

README updated:
  - Day-to-day shortcuts code block adds two bullets after Tree
  - Stale-banner sentence extended to mention the two new commands
  - Out-ConsoleGridView consumer list extended
  - Long intro paragraph mentions the two new commands

Diagrams updated (docs/azure-devops-diagrams.md):
  - Diagram 1 (high-level architecture): adds ShowAreas + ShowIters
    nodes in Public cluster; cache-first edges to AreasJson / IterJson
    plus dotted live-fallback edges to AzBoards
  - Diagram 9 (function dependency map): adds the two public nodes,
    plus a "Classification tree views" cluster with the three new
    private helpers, edges into ReadCls / InvCls / Stale / ShowRows /
    Indent

https://claude.ai/code/session_01P7znUKccAohZYEwYRBNVfo
Copy link
Copy Markdown
Owner Author

🐚 Senior Bash Engineer

Summary

PR #45 adds Show-AzDevOpsAreas and Show-AzDevOpsIterations as a PowerShell-only change (per the Shell Parity Note on issue #44). The diff touches README.md, docs/azure-devops-diagrams.md, and powcuts_by_cli/azdevops_workitems.ps1. No files under bashcuts_by_cli/ or .bcut_home were modified.

Files Changed (bash scope)

README.md                               (docs)
docs/azure-devops-diagrams.md           (docs)
powcuts_by_cli/azdevops_workitems.ps1   (PowerShell — out of scope)

Checks Performed

  • git diff origin/main...HEAD --name-only — confirmed zero bash files in the diff
  • Spot-checked README/diagram diff for embedded bash snippets, $(...) expansions, alias definitions, or start/open invocations — no bash code added
  • Sourcing wire-up (.bcut_home) — N/A, no new bash files
  • Naming convention (kebab-case verb-noun) — N/A, no new bash aliases/functions
  • macOS start portability, quoting, bashism, side-effects — N/A

Sourcing Wire-Up

N/A — no new bash files added.

Naming Convention

N/A — no new bash aliases or functions.

Verdict

APPROVE — no bash files in this diff. This is a PowerShell-only PR per the Shell Parity Note; nothing in the bash surface area is at risk. Deferring all substantive review to the Senior PowerShell Engineer.

https://claude.ai/code/session_01P7znUKccAohZYEwYRBNVfo


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

💠 Senior PowerShell Engineer

Verdict: APPROVE

Reviewed powcuts_by_cli/azdevops_workitems.ps1 (+177) — five new functions plus README/diagram updates.

Checks

  • Parse: SKIPPED — pwsh not on PATH. Brace/paren balance verified via awk: 569/569 braces, 823/823 parens — clean.
  • Approved verbs: Get-, Format-, Show- are all in Get-Verb. The az-Show-AzDevOps* public-wrapper convention follows the established project pattern (PR feat(#25): prefix user-facing Azure DevOps PowerShell functions with az- #26 precedent) — non-approved verb on the wrapper is intentional and consistent.
  • Parameter binding: [Parameter(Mandatory)] [ValidateSet('Iteration', 'Area')] [string] $Kind applied uniformly across all three internal helpers — ValidateSet catches typos at bind time. [CmdletBinding()] param() on the two public wrappers is correct minimalism.
  • Output streams: Write-Host correctly reserved for status/banners (yellow live-fetch hint, "no Xs defined" notice). The text-tree fallback uses Write-Output (Format-AzDevOpsClassificationNode ...) — pipeline-capturable as it should be. Clean separation.
  • Date parsing: ($Node.attributes.startDate -as [datetime]).ToString('yyyy-MM-dd') is properly guarded by if ($Node.attributes -and $Node.attributes.startDate)$null can't reach .ToString.
  • List collection: New-Object System.Collections.Generic.List[PSCustomObject] with $rows.Add($row) is correct — .Add returns void on a generic List (no need to reassign or pipe to Out-Null, unlike ArrayList).
  • ,@($rows) return: matches the existing Get-AzDevOpsTreeRows (line ~1506) convention; prevents PS unwrapping a single-element collection. Consistent.
  • Nested & $walk scriptblock recursion: idiomatic enough — it captures $Kind and $rows from the enclosing scope cleanly via dynamic scoping, and is the standard PS pattern for in-function recursion (you can't define a nested function and recurse into it from the same scope without dot-sourcing tricks). Tree depth for AzDO areas/iterations is bounded (typically <10), so stack risk is nil. Reads fine.
  • CLAUDE.md style rules: multi-line if/else blocks expanded properly (no inline shorthand), named locals ($startIso, $finishIso, $arrowGlyph, $publicCommand, $kindLabelLow) instead of magic strings, two blank lines between top-level functions, intermediate $result / $empty locals before return (compliant with the never-return-a-call-directly rule), [char]0x2192 hoisted into $arrowGlyph with explanatory comment.

Minor notes (non-blocking)

  • $cameFromCache = $true initial assignment is set before the cache lookup completes — slightly easier to read if assigned right after Read-AzDevOpsClassificationCache returns non-null, but the current shape is fine and the logic is correct.
  • The $kindLabelLow = "${azKind}s" naive pluralization is OK for "areas"/"iterations"; would break for irregular plurals, but ValidateSet constrains $Kind to the two safe values.

Ship it.

https://claude.ai/code/session_01P7znUKccAohZYEwYRBNVfo


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

🛡️ Senior Security Engineer

Verdict: APPROVE

Reviewed powcuts_by_cli/azdevops_workitems.ps1 (+177), README.md, and docs/azure-devops-diagrams.md against the six risk vectors flagged in the brief.

Findings

  1. Live-fallback / shell-injection surface — clean. Show-AzDevOpsClassification only reaches Invoke-AzDevOpsClassificationLive -Kind $Kind when the cache is absent, after Assert-AzDevOpsAuthOrAbort gates it. $Kind is locked to [ValidateSet('Iteration','Area')] at every hop (Show-AzDevOpsClassification, Invoke-AzDevOpsClassificationLive, Get-AzDevOpsClassificationList). Downstream in azdevops_db.ps1:190, the az invocation uses & az @scopedArgs --output json — the call-operator splat passes a [string[]] as separate argv tokens, so no shell parser ever sees the string. Env-var injection of --organization / --project is appended as discrete array elements, also not concatenated. No new injection surface.

  2. Cache file reads — clean. Read-AzDevOpsClassificationCache (existing, unchanged) is consumed by-value; the new code never derives a path from user/JSON input. Get-Content -LiteralPath is used, blocking wildcard expansion. No new traversal surface.

  3. Date parsing on JSON — safe. ($Node.attributes.startDate -as [datetime]).ToString('yyyy-MM-dd') uses the -as operator which returns $null on parse failure (no exception, no execution). [datetime] parsing has no deserialization gadget paths in PowerShell. Defense-in-depth holds.

  4. Output streams — clean. Data flows through Write-Output and Show-AzDevOpsRows (grid view). Write-Host is used only for two human-facing status strings ("(fetching $kindLabelLow live …)" and "(no $kindLabelLow defined)") — neither echoes tokens, env vars, or raw JSON.

  5. -CommandName argument — clean. $publicCommand = "az-Show-AzDevOps$($Kind)s" is built from the validated $Kind enum, then passed to Assert-AzDevOpsAuthOrAbort -CommandName $publicCommand as a bound [string] parameter (no Invoke-Expression, no splatting into a shell). Worst case is a misformatted log line — not an injection.

  6. README + diagrams — clean. Documentation only; no curl|sh, no embedded credentials, no executable snippets.

Notes

  • pwsh unavailable in this sandbox; parse-check deferred to the bash/PS lanes.
  • Read-only feature, env-var-scoped, validated enum surface end-to-end. No new attack surface introduced.

https://claude.ai/code/session_01P7znUKccAohZYEwYRBNVfo


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

🧼 Senior Clean-Code Engineer

Summary

One file of substance changed — powcuts_by_cli/azdevops_workitems.ps1 (+177 lines, 5 new functions). The diff is well-factored: the two public twins delegate to a shared Show-AzDevOpsClassification -Kind orchestrator (CLAUDE.md "extract repeated branches" rule satisfied proactively), the formatter is its own helper, and the row-flattener is its own helper. Breathing room is consistent (two blank lines between top-level functions, internal blanks separating phases). Magic glyph 0x2192 is named $arrowGlyph. Verdict is APPROVE with two LOW polish notes and one MEDIUM observation that's deferrable.

Findings

Severity Location Issue
MEDIUM Show-AzDevOpsClassification lines 2013–2024 vs Get-AzDevOpsClassificationPaths lines 1879–1884 Both functions implement the same cache-first-then-live pattern: Read-AzDevOpsClassificationCache → if null, Write-Host "(fetching ${kind}s live - run az-Sync-AzDevOpsCache to make this instant)"Invoke-AzDevOpsClassificationLive. The new function adds an auth-gate and a $cameFromCache flag, but the core "load tree, note source" decision is now duplicated across two functions. Candidate extraction: Get-AzDevOpsClassificationTreeWithSource -Kind <K> [-CommandName <name>] returning @{ Tree=…; CameFromCache=$true/$false } (or $null if auth aborts). The picker's Get-AzDevOpsClassificationPaths could ignore the source field; the new orchestrator uses it for the stale banner. Deferrable — flagged so the third caller doesn't re-paste the block.
LOW Get-AzDevOpsClassificationRows line 1927 (if ($null -eq $Node) { return }) Inline if-shorthand. CLAUDE.md bans single-line if (cond) { x }. That said, the existing ConvertTo-AzDevOpsClassificationPaths line 1851 uses the identical if ($null -eq $node) { continue } shape, so this is matching pre-existing project convention for null-guards. Either expand both or leave both — don't fix one in isolation. Not blocking.
LOW Get-AzDevOpsClassificationRows lines 1919–1922 The empty-rows early return collapses $empty = ,@($rows); return $empty into a tight 4-line block. Reads fine; included only to note that the $result = …; return $result pattern lower in the same function (lines 1973–1974) is the cleaner template — minor consistency nit.

Duplication Map

Pattern Callsites Proposed helper
Cache-first / live-fallback tree load with "(fetching … live - run az-Sync-AzDevOpsCache to make this instant)" notice Get-AzDevOpsClassificationPaths:1879–1884, Show-AzDevOpsClassification:2013–2024 Get-AzDevOpsClassificationTreeWithSource -Kind <K> [-CommandName <name>] returning @{ Tree; CameFromCache }

Function Sizes

Function Lines (body) Verdict
az-Show-AzDevOpsAreas 3 OK — wrapper
az-Show-AzDevOpsIterations 3 OK — wrapper
Format-AzDevOpsClassificationNode 9 OK
Show-AzDevOpsClassification 40 OK — orchestrator with distinct phases (load → guard → render) separated by blank lines
Get-AzDevOpsClassificationRows 58 OK — recursion via & $walk is justified (DFS preserving sibling declaration order AND tracking depth); the existing iterative ConvertTo-AzDevOpsClassificationPaths discards order and depth, so a stack-based rewrite here would have to reverse-push children and carry (node,depth) tuples — not flatter

Notes — Items Verified Clean

  • Public twins share Show-AzDevOpsClassification -Kind — the named CLAUDE.md "parallel function pairs" rule is satisfied proactively.
  • if/else for date parsing ($startIso = if … { … } else { … }) and for the row-shape branch are properly expanded multi-line per CLAUDE.md.
  • $arrowGlyph = "$([char]0x2192)" follows the named-magic-literals rule.
  • $result = ,@($rows); return $result follows the never-return-a-call-directly rule.
  • Iteration vs Area row-shape branch (StartDate/FinishDate columns) is schema-driven and not a candidate for extraction without inventing parallel per-kind row factories.
  • Breathing room (two blank lines between top-level functions, one blank line between logical groups inside Show-AzDevOpsClassification) is consistent.

Verdict

APPROVE — well-factored. One MEDIUM duplication to consider before a third caller appears, two LOW polish notes; none block merge.

https://claude.ai/code/session_01P7znUKccAohZYEwYRBNVfo


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add az-Show-AzDevOpsAreas and az-Show-AzDevOpsIterations — keyboard-discoverable classification-tree viewers

2 participants