Skip to content

feat: add workstream column discovery tool#23

Open
ghelleks wants to merge 3 commits into
crunchtools:mainfrom
ghelleks:feat/workstream-columns
Open

feat: add workstream column discovery tool#23
ghelleks wants to merge 3 commits into
crunchtools:mainfrom
ghelleks:feat/workstream-columns

Conversation

@ghelleks
Copy link
Copy Markdown
Contributor

Summary

  • Adds a dedicated get_workstream_columns tool that returns the custom Kanban column catalog (column_id + column_name) for a workstream, without fetching full action item details
  • Enriches get_workstream_activities to include a top-level columns list when custom columns are present, so callers can see the board structure alongside the cards
  • Column data is derived from ai_column on action items since the WorkBoard API has no dedicated column-listing endpoint

Context

Issue #18 added ai_column passthrough for placing cards in specific columns (v0.8.0). This PR completes the picture by letting callers discover what columns exist before creating/moving cards.

Probing the live API confirmed there is no workstream-level column metadata — the ws_* keys are identical whether or not custom columns are configured. Columns only exist as ai_column on individual action items, so this implementation scans activities and deduplicates.

Limitation: empty columns (with no cards) won't appear in the list.

Test plan

  • 103 tests pass (6 new: multi-column dedup, no-columns absent key, dedicated tool with/without columns, tool count bump)
  • Manual verification against live workstreams with custom columns (RHELBU EXT STAFF, RHEL Forever Program, RHELBU Roadmap)

The WorkBoard API exposes custom Kanban column placement via ai_column
on action items, but there was no way to discover which columns a
workstream has. This adds:

- get_workstream_columns tool: dedicated tool that returns the
  deduplicated column catalog (column_id + column_name) for a
  workstream, without returning all action item details
- Column metadata on get_workstream_activities: the activities
  response now includes a top-level "columns" list when custom
  columns are present
- 6 new tests covering multi-column dedup, no-columns, and the
  dedicated columns tool

Closes crunchtools#18 (column discovery complement to the column placement
support added in v0.8.0).
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new tool, workboard_get_workstream_columns_tool, to retrieve custom Kanban columns for a workstream. It includes updates to the server, tool exports, and the core logic in workstreams.py to extract and deduplicate column data from activity items. Corresponding tests have been added, and the tool count has been updated. Feedback suggests improving the robustness of API response handling to prevent potential AttributeError when keys are null and recommends refactoring duplicated column extraction logic into a shared helper function.

response = await client.get(f"/workstream/{ws_id}/activity")
activity_body = response.get("data", {})

ws_data = activity_body.get("workstream", {})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the API response contains "workstream": null, activity_body.get("workstream", {}) will return None because the key exists in the dictionary. This will cause an AttributeError on line 245 when calling .get() on a NoneType. A more robust check is needed to ensure ws_data is always a dictionary.

Suggested change
ws_data = activity_body.get("workstream", {})
ws_data = activity_body.get("workstream")
if not isinstance(ws_data, dict):
ws_data = {}
References
  1. Ensure robust handling of API responses where expected keys might contain null values to prevent runtime errors.

Comment on lines +243 to +262
columns: dict[str, str] = {}

activity_data = ws_data.get("ws_activity", {})
if isinstance(activity_data, dict):
activities = activity_data.get("activity", [])
if isinstance(activities, list):
for ai in activities:
if not isinstance(ai, dict):
continue
col = ai.get("ai_column")
if isinstance(col, dict) and col.get("id"):
columns[col["id"]] = col.get("name", "")

return {
"ws_id": ws_data.get("ws_id", str(ws_id)),
"name": ws_data.get("ws_name", ""),
"columns": [
{"column_id": cid, "column_name": cname}
for cid, cname in columns.items()
],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for extracting unique columns from action items is duplicated here and in _format_workstream (lines 138-149). Extracting this logic into a shared helper function would improve maintainability and ensure that any future changes to column discovery (e.g., handling empty columns if the API changes) only need to be implemented once.

References
  1. Follow the DRY (Don't Repeat Yourself) principle by extracting repeated logic into reusable helper functions to improve maintainability.

Address Gemini code review feedback:
- Extract duplicated column extraction logic into shared
  _extract_columns() helper used by both _format_workstream
  and get_workstream_columns
- Guard against "workstream": null in API response to prevent
  AttributeError when calling .get() on NoneType
Gourmand reports _extract_columns as single-use, but it is called
from two distinct functions: _format_workstream and
get_workstream_columns. Filed as gourmand_bug classification.
@ghelleks ghelleks force-pushed the feat/workstream-columns branch from cf7bcf8 to ed946ea Compare April 15, 2026 01:30
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.

1 participant