feat: add workstream column discovery tool#23
Conversation
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).
There was a problem hiding this comment.
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", {}) |
There was a problem hiding this comment.
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.
| ws_data = activity_body.get("workstream", {}) | |
| ws_data = activity_body.get("workstream") | |
| if not isinstance(ws_data, dict): | |
| ws_data = {} |
References
- Ensure robust handling of API responses where expected keys might contain null values to prevent runtime errors.
| 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() | ||
| ], |
There was a problem hiding this comment.
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
- 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.
cf7bcf8 to
ed946ea
Compare
Summary
get_workstream_columnstool that returns the custom Kanban column catalog (column_id + column_name) for a workstream, without fetching full action item detailsget_workstream_activitiesto include a top-levelcolumnslist when custom columns are present, so callers can see the board structure alongside the cardsai_columnon action items since the WorkBoard API has no dedicated column-listing endpointContext
Issue #18 added
ai_columnpassthrough 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 asai_columnon individual action items, so this implementation scans activities and deduplicates.Limitation: empty columns (with no cards) won't appear in the list.
Test plan