Skip to content

Add az-Show-RecentActivity — merged "posted or tagged" work-item view#151

Open
jdschleicher wants to merge 3 commits into
mainfrom
claude/az-show-recent-activity-mp0lkm
Open

Add az-Show-RecentActivity — merged "posted or tagged" work-item view#151
jdschleicher wants to merge 3 commits into
mainfrom
claude/az-show-recent-activity-mp0lkm

Conversation

@jdschleicher

@jdschleicher jdschleicher commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Contents

Section Status
Summary
Issue Closes #149
Changes ✅ 6 files
Design notes
Test Plan 4 manual items
Skill Reports
🐚 Bash Engineer Review ✅ APPROVE — View
💠 PowerShell Engineer Review ✅ APPROVE — View
🛡️ Security Audit ✅ PASS — View
🧼 Clean-Code Engineer Review ✅ APPROVE — View
✅ Criteria Check ✅ PASS (11/11, 4 manual) — View
📚 Docs Check ✅ CURRENT (README + AzDO diagrams updated)

How it works

az-Sync-AzDevOpsCache now fills a new activity.json cache from a [System.ChangedBy] = @Me WIQL. On demand, az-Show-RecentActivity reads that cache plus the existing mentions.json, merges them (dedupe by Id, tag each row Posted/Tagged/Both), filters to non-closed, sorts newest-first, and hands the grid selection to the shared open/create-child action. New nodes are :::pub / :::priv; pre-existing helpers are referenced by name only.

flowchart TD
    Sync([az-Sync-AzDevOpsCache]):::pub
    ActDS["activity dataset<br/>WIQL System.ChangedBy = @Me"]:::priv
    ActJson[(activity.json)]:::io
    MenJson[(mentions.json)]:::io

    Entry([az-Show-RecentActivity]):::pub
    ReadAct["Read-AzDevOpsActivityCache<br/>→ ConvertFrom-AzDevOpsActivityItem"]:::priv
    ReadM["Read-AzDevOpsMentionsCache<br/>(existing)"]:::priv
    Bail{neither cache?}
    Abort([return — missing-cache hint])
    Merge["Merge-AzDevOpsActivityRows<br/>dedupe by Id · Reason Posted/Tagged/Both"]:::priv
    Filter["Select-AzDevOpsActiveItems<br/>non-closed (existing)"]:::priv
    Sort["Sort-AzDevOpsByDateDesc<br/>newest by ChangedDate (existing)"]:::priv
    Show["Show-AzDevOpsRows grid (existing)"]:::priv
    Action["Invoke-AzDevOpsRowAction<br/>open / create child (existing)"]:::priv
    Done([open in browser])

    Sync --> ActDS --> ActJson

    Entry --> ReadAct -.reads.-> ActJson
    Entry --> ReadM -.reads.-> MenJson
    ReadAct --> Bail
    ReadM --> Bail
    Bail -- both null --> Abort
    Bail -- some data --> Merge
    ReadAct --> Merge
    ReadM --> Merge
    Merge --> Filter --> Sort --> Show --> Action --> Done

    classDef pub fill:#1f3a5f,stroke:#4ea3ff,color:#fff
    classDef priv fill:#3a3a3a,stroke:#999,color:#fff
    classDef io fill:#5a4a1a,stroke:#e0c060,color:#fff
Loading

Summary

Adds az-Show-RecentActivity, a single selectable grid of the non-closed Azure DevOps work items you've been active on lately — items you posted/commented on (new activity dataset: [System.ChangedBy] = @Me) unioned with items you were @-tagged in (existing mentions cache). Rows are deduped by Id, tagged with a Reason column (Posted / Tagged / Both), filtered to non-closed states, and sorted newest-first by ChangedDate. Selection flows through the shared Invoke-AzDevOpsRowAction (open in browser / create child) like the other az-Show-* views.

Issue

Closes #149

Changes

  • powcuts_by_cli/azdevops_paths.ps1 — new activity.json cache path, ActivityQuery config path, $script:AzDevOpsDefaultActivityWiql ([System.ChangedBy] = @Me), activity entry in Get-AzDevOpsQueryDefaults, and 'activity' added to the Get-AzDevOpsWiql ValidateSet + cache-listing doc comment.
  • powcuts_by_cli/azdevops_sync.ps1 — new activity dataset in Get-AzDevOpsSyncDatasets, so az-Sync-AzDevOpsCache writes activity.json.
  • powcuts_by_cli/azdevops_views.ps1ConvertFrom-AzDevOpsActivityItem, Read-AzDevOpsActivityCache, Merge-AzDevOpsActivityRows, and the public az-Show-RecentActivity.
  • powcuts_by_cli/azdevops_help.ps1az-help catalog entry for the new view + updated sync purpose string.
  • README.mdaz-Show-RecentActivity added to the Azure DevOps prose + usage examples.
  • docs/azure-devops-diagrams.md — new view + activity dataset/cache reflected in the architecture (Feature/oscheckforopen #1), sync fan-out (updates #4, five→six), cache-consumers note (adjustments to make reinit a function that works with both bashrc and… #5), and dependency map (Add New-AzDevOpsUserStory with interactive parent-feature + iteration/area pickers #12).

Design notes

  • Data source: [System.ChangedBy] = @Me is a best-effort proxy for "I posted/commented on it" — WIQL has no first-class "I authored a revision" predicate. Accepted for v1 (see the issue's open question).
  • Dedupe keying: the merge uses a plain hashtable keyed by integer Id rather than [ordered], whose [int] indexer is positional rather than key-based; the caller re-sorts by ChangedDate, so insertion order is irrelevant.
  • Shell parity: PowerShell-only — the Azure DevOps cache is a PowerShell-only subsystem with no bash counterpart.

Test Plan

  • az-Sync-AzDevOpsCache reports an activity dataset row count and writes ~/.bashcuts-az-devops-app/cache/activity.json
  • az-Show-RecentActivity opens newest-first, no closed items, Reason column shows Posted / Tagged / Both
  • Tab-tab on az-Show- lists the new function
  • Selecting a row opens it in the browser

🤖 Generated with Claude Code

Adds a new "recent activity" view that unions the work items the user
posted on (new activity dataset: System.ChangedBy = @me) with items they
were @-tagged in (existing mentions cache), dedupes by Id, tags each row
Posted/Tagged/Both, filters to non-closed, and sorts newest-first by
ChangedDate.

- azdevops_paths.ps1: activity.json cache path, ActivityQuery config path,
  default activity WIQL, activity in query defaults + Get-AzDevOpsWiql
  ValidateSet
- azdevops_sync.ps1: activity dataset in Get-AzDevOpsSyncDatasets
- azdevops_views.ps1: ConvertFrom-AzDevOpsActivityItem,
  Read-AzDevOpsActivityCache, Merge-AzDevOpsActivityRows, az-Show-RecentActivity
- azdevops_help.ps1: az-help catalog entry + sync purpose string

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GN1LLuN9FtDj1F9X9itcWg

Copy link
Copy Markdown
Owner Author

🐚 Senior Bash Engineer

Summary

This PR changes only PowerShell files (powcuts_by_cli/azdevops_{paths,sync,views,help}.ps1). No files under bashcuts_by_cli/ or .bcut_home were modified, so there is nothing in scope for a bash review.

Verdict

APPROVE — no bash files in this diff


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

🧼 Senior Clean-Code Engineer

Summary

Four azdevops_*.ps1 files changed to add a recent-activity view. azdevops_views.ps1 adds ConvertFrom-AzDevOpsActivityItem / Read-AzDevOpsActivityCache / Merge-AzDevOpsActivityRows / az-Show-RecentActivity; the other three wire in the activity dataset (path, WIQL default, sync entry, help catalog). The new code faithfully mirrors the established per-dataset converter+reader+view shape, reuses every shared helper (Select-AzDevOpsActiveItems, Sort-AzDevOpsByDateDesc, Show-AzDevOpsRows, Invoke-AzDevOpsRowAction, Write-AzDevOpsStaleBanner), and respects the named CLAUDE.md formatting rules. No blocking duplication.

Findings

Severity Location Issue
LOW azdevops_views.ps1 Merge-AzDevOpsActivityRows (~705-755) The two [PSCustomObject]@{ Id/Type/State/Title/Reason/ChangedDate } row literals differ only by Reason and the date source ($item.ChangedDate vs $item.MentionedAt). Minor; a New-AzDevOpsActivityRow -Item $i -Reason $r -ChangedDate $d private helper would dedupe ~7 lines. Borderline against the premature-abstraction rule (one function, two callsites) — leaving as-is is defensible. Suggest, don't block.
LOW azdevops_views.ps1 ConvertFrom-AzDevOpsActivityItem vs ConvertFrom-AzDevOpsMentionItem The Id-fallback and ChangedDate if/else blocks are byte-identical between the two converters. This is the repo's intentional one-converter-per-dataset pattern (4+ datasets follow it); output shapes genuinely differ (activity has no MentionedBy, emits ChangedDate not MentionedAt). Extraction would fight the established pattern and trip the premature-abstraction check. Not a finding to action — noted only to confirm it was assessed.

Duplication Map

Pattern Callsites Proposed helper
activity-row [PSCustomObject] literal Merge-AzDevOpsActivityRows posted-branch, tagged-branch New-AzDevOpsActivityRow (LOW — optional, borderline premature)
Id-fallback + ChangedDate if/else ConvertFrom-AzDevOpsActivityItem, ConvertFrom-AzDevOpsMentionItem none — intentional per-dataset pattern, do not extract

Function Sizes

Function Lines (body) Verdict
ConvertFrom-AzDevOpsActivityItem ~25 OK — single responsibility, mirrors mention converter
Read-AzDevOpsActivityCache 6 OK — captures $items, returns named local (CLAUDE.md compliant)
Merge-AzDevOpsActivityRows ~45 OK — one cohesive union; two short foreach blocks with breathing room
az-Show-RecentActivity ~25 OK — thin orchestrator over shared helpers

CLAUDE.md rule checks

  • Named magic values$reasonPosted / $reasonTagged / $reasonBoth hoisted to named locals. PASS.
  • Multi-line if/else — every if/else (Id fallback, ChangedDate, existing-row check) expanded across lines, K&R joiners. PASS.
  • Never return a function call directlyRead-AzDevOpsActivityCache captures $items, Merge-AzDevOpsActivityRows captures $merged, both return the named local. PASS.
  • Breathing room — two blank lines between the new top-level functions; one blank line between logical groups inside Merge (the two foreach blocks, the final $merged). PASS.
  • No side effects at source time — only function definitions and $script:-scoped WIQL constant added; nothing runs on dot-source. PASS.

Verdict

APPROVE — well-factored, no blocking duplication. The new functions correctly reuse the shared view/sort/render/action helpers and follow the established per-dataset pattern; the only notes are two LOW suggestions, one of which (the converter "duplication") is the repo's deliberate convention and should be left alone.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

💠 Senior PowerShell Engineer

Summary

Four .ps1 files changed to add the az-Show-RecentActivity view: a new activity WIQL dataset (azdevops_paths.ps1), wiring it into the sync engine (azdevops_sync.ps1), the public view + three private helpers (azdevops_views.ps1), and a help-catalog entry (azdevops_help.ps1). The code is clean, follows the established az-Show-* scaffolding, reuses existing shared helpers correctly, and adds no new files (so no dot-sourcing wire-up risk). pwsh is not on PATH — reviewed statically.

Findings

Severity Location Issue
LOW azdevops_views.ps1 Merge-AzDevOpsActivityRows (Both branch) When an item appears in both caches, only Reason is flipped to 'Both'; the existing row keeps the activity ChangedDate and the mentions MentionedAt is discarded. This is a deliberate, defensible single-source-of-truth choice (the comment explains the date plumbing), and the caller re-sorts on the one ChangedDate key. Worth a one-line note that the two dates can differ; no change required.
LOW (info) azdevops_views.ps1 Merge-AzDevOpsActivityRows, Read-AzDevOpsActivityCache These two helpers omit param() mandatory/typing that the public function uses, but they mirror the existing Read-AzDevOpsMentionsCache / converter style exactly. Consistent with neighbors — no action.

Detailed checks

Approved verbs — PASS. All four new function names use approved verbs: Show (Common), ConvertFrom (Data), Read (Communications), Merge (Data). ConvertFrom-AzDevOpsActivityItem, Read-AzDevOpsActivityCache, Merge-AzDevOpsActivityRows, az-Show-RecentActivity all parse as Verb-Noun.

Parameter binding — PASS. az-Show-RecentActivity uses [CmdletBinding()] + [string[]] $State, mirroring az-Get-AzDevOpsMentions. ConvertFrom-AzDevOpsActivityItem correctly marks $Raw mandatory. Helper calls bind correctly: the activity converter emits ChangedDate; the mentions converter emits MentionedAt; Merge-AzDevOpsActivityRows reads $item.ChangedDate from the activity branch and $item.MentionedAt from the mentions branch — verified against ConvertFrom-AzDevOpsMentionItem (line 557). The final Select-Object Id, Type, State, Reason, (Get-AzDevOpsTitleColumn), ChangedDate produces the Id/Type/Title columns that the downstream Invoke-AzDevOpsRowActionGet-AzDevOpsRowId / Resolve-AzDevOpsRowType consume.

Output streams — PASS. Write-Host is used only for the user-facing empty-state hint ((no open posted-or-tagged activity in cache)), which is correct status messaging — no data is emitted through Write-Host. Data flows via return/pipeline.

Error handling / cache absence — PASS. Read-AzDevOpsJsonCache returns $null for a missing cache (each reader prints its own hint) and @() for present-but-empty. The early bail if ($null -eq $activity -and $null -eq $mentions) { return } correctly distinguishes "neither cache present" from "present but empty", so the view only short-circuits when there is genuinely nothing to read. No az calls in the view path — read-only from cache, consistent with the other az-Show-* views.

Merge dictionary (plain hashtable keyed by [int] Id) — CORRECT. Confirmed the design rationale: a plain @{} uses the key-based [object] indexer, so $byId[$item.Id] is a true key lookup. [ordered]'s [int] indexer is positional, which would silently misbehave here (looking up "the Nth entry" instead of the item whose Id == N). Since the caller re-sorts by ChangedDate, insertion order is irrelevant and [ordered] buys nothing while introducing the positional-indexer footgun. The Where-Object { $null -ne $_ } guards in both foreach loops correctly skip the $null that an absent cache reads back as. The author's choice and the explanatory comment are both sound.

Side effects / profile load — PASS. All additions are inside function bodies or $script:-scoped WIQL/here-string/catalog data assigned at source time in the established pattern. No new unconditional output, network, or blocking calls.

WIQL dataset wiring — PASS. activity is threaded end-to-end: default WIQL + cache path + config query path (azdevops_paths.ps1), added to Get-AzDevOpsQueryDefaults and the Get-AzDevOpsWiql ValidateSet, and registered as an AsArray dataset in Get-AzDevOpsSyncDatasets with a .GetNewClosure() fetch block matching the assigned/mentions datasets.

Dot-Sourcing Wire-Up

WIRED — no new .ps1 files added; all changes land in files already dot-sourced from powcuts_home.ps1.

Approved Verbs

PASS — all new function names use approved verbs (Show, ConvertFrom, Read, Merge).

Verdict

APPROVE — no blocking issues. Two LOW/informational notes only (both-source date discard is an intentional, documented design choice).


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

✅ Criteria Check — #149: Add az-Show-RecentActivity

Syntax Checks

  • ✅ Bash: no bash files changed (N/A)
  • ⏭️ PowerShell: SKIPPEDpwsh not on PATH in this environment. Reviewed statically; brace/paren balance verified on all 4 files.

Acceptance Criteria

New activity dataset

# Criterion Status Evidence
1 activity.wiql default ([System.ChangedBy] = @Me) ✅ VERIFIED azdevops_paths.ps1:219-220
2 activity in Get-AzDevOpsQueryDefaults ✅ VERIFIED azdevops_paths.ps1:276-278
3 activity in Get-AzDevOpsWiql ValidateSet ✅ VERIFIED azdevops_paths.ps1:335
4 ActivityQuery config path + activity.json cache path ✅ VERIFIED azdevops_paths.ps1:236, :68
5 activity dataset in Get-AzDevOpsSyncDatasets ✅ VERIFIED azdevops_sync.ps1:82,107-110

az-Show-RecentActivity view

# Criterion Status Evidence
6 Reads activity + mentions cache only; bails if neither exists ✅ VERIFIED azdevops_views.ps1:766-771 (Read-AzDevOpsActivityCache + Read-AzDevOpsMentionsCache, if ($null -eq $activity -and $null -eq $mentions) { return })
7 Union + dedupe by Id + Reason Posted/Tagged/Both ✅ VERIFIED Merge-AzDevOpsActivityRows :703-757 (Reason at :732,:741,:750)
8 Non-closed filter + -State override ✅ VERIFIED :763 [string[]] $State, :777 Select-AzDevOpsActiveItems
9 Newest-first by ChangedDate (nulls last) ✅ VERIFIED :783 Sort-AzDevOpsByDateDesc -Field 'ChangedDate'
10 Renders Id/Type/State/Reason/Title(trunc)/ChangedDate → Invoke-AzDevOpsRowAction ✅ VERIFIED :785 projection (Get-AzDevOpsTitleColumn), :789-790
11 Stale banner ✅ VERIFIED :773 Write-AzDevOpsStaleBanner
12 pwsh parse — zero errors ⏭️ SKIPPED pwsh unavailable; static review + brace balance

Summary

Status Count
✅ VERIFIED 11
⏭️ SKIPPED (pwsh) 1
🔍 MANUAL 4 (below)

Manual Verification Checklist (fresh PowerShell terminal)

  • az-Sync-AzDevOpsCache → reports an activity row count and writes ~/.bashcuts-az-devops-app/cache/activity.json
  • az-Show-RecentActivity → grid opens newest-first, no closed items, Reason shows Posted/Tagged/Both
  • Tab-tab on az-Show- lists az-Show-RecentActivity
  • Select a row → opens in browser

Verdict

PASS — all 11 automated criteria verified; 1 parse gate skipped (no pwsh in CI env); 4 items need hands-on terminal testing.


Generated by Claude Code

- README: add az-Show-RecentActivity to the Azure DevOps prose enumeration
  and the usage-examples block
- azure-devops-diagrams.md: add the new view + activity dataset/cache to the
  high-level architecture (#1), sync dataset fan-out (#4, five->six), cache
  consumers note (#5), and function dependency map (#12)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GN1LLuN9FtDj1F9X9itcWg

Copy link
Copy Markdown
Owner Author

🛡️ Security Audit

Verdict: PASS — no security vulnerabilities found.

The change adds a static WIQL query, a cache-only read view, and dataset wiring that reuses existing, already-hardened helpers. Each focus area was traced end-to-end:

  • WIQL / template injection — Not vulnerable. $script:AzDevOpsDefaultActivityWiql (Where [System.ChangedBy] = @Me) is a fully static string with no {{AZ_USER_EMAIL}} / {{AZ_AREA}} placeholders, so Get-AzDevOpsWiql -Name 'activity' performs no token substitution. @Me is a server-side Azure DevOps WIQL macro, not client-side interpolation. No user/env input reaches the query.
  • Command / argument construction to az — Not vulnerable. The fetch closure calls the existing Invoke-AzDevOpsBoardsQuery -Wiql $activityWiql, which passes the WIQL through a native argument array (not a shell string). The WIQL stays one discrete argument; no metacharacter expansion.
  • JSON deserialization of cached files — Not vulnerable. Read-AzDevOpsActivityCacheRead-AzDevOpsJsonCache uses Get-Content -Raw | ConvertFrom-Json in try/catch. ConvertFrom-Json yields only PSCustomObject/arrays/primitives — no type-directed deserialization risk. ConvertFrom-AzDevOpsActivityItem does only safe [int]/[datetime] casts.
  • Path handling / traversal — Not vulnerable. Activity = Join-Path $cacheDir 'activity.json' and ActivityQuery = Join-Path $queriesDir 'activity.wiql' use hardcoded constant filenames; the base dir derives from internal config, no caller-supplied component. Reads use -LiteralPath.

No findings reached the HIGH/MEDIUM confidence threshold.


Generated by Claude Code

…-activity-mp0lkm

# Conflicts:
#	powcuts_by_cli/azdevops_help.ps1
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-RecentActivity — merged "posted or tagged" work-item view, newest-first, open items only

2 participants