feat: Add openspec-sync-specs and openspec-verify-change skills
- Introduced `openspec-sync-specs` skill to sync delta specs to main specs, allowing intelligent merging of requirements. - Added `openspec-verify-change` skill to verify implementation against change artifacts, ensuring completeness, correctness, and coherence before archiving. docs: Create CLAUDE.md for project guidance - Added CLAUDE.md to provide an overview of the PCLI project, including build, test commands, architecture, and resource addition guidelines. chore: Add new change and design documents for project filter in status command - Created `.openspec.yaml`, `design.md`, `proposal.md`, and `tasks.md` for the `add-project-filter-to-status` change. - Updated specs for CLI commands and status command to include project filtering functionality. feat: Expand board included parsing in API client - Added parsing for `labels`, `cardLabels`, and `cardMemberships` in the `GetBoard` response. - Updated `ListCardsByBoard` to enrich card output with label names, enhancing usability in kanban sync workflows.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-18
|
||||
@@ -0,0 +1,37 @@
|
||||
## Context
|
||||
|
||||
The Planka v2 API `GET /api/boards/:id` response includes an `included` object with: `users`, `boardMemberships`, `labels`, `lists`, `cards`, `cardMemberships`, `cardLabels`, `taskLists`, `tasks`, `attachments`, `customFieldGroups`, `customFields`, `customFieldValues`. Currently `GetBoard` only parses `lists` and `cards`, discarding everything else.
|
||||
|
||||
The `CardLabel` and `Label` types already exist in `model/types.go`. The `Board` struct has `Lists` and `Cards` fields but not `Labels`, `CardLabels`, or `CardMemberships`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Parse `labels`, `cardLabels`, and `cardMemberships` from the `GetBoard` response
|
||||
- Make label data available on the `Board` struct for downstream consumers
|
||||
- Enrich `ListCardsByBoard` output so `card list --board` includes label names per card
|
||||
|
||||
**Non-Goals:**
|
||||
- Parsing all included fields (users, taskLists, attachments, customFields, etc.) — only what's needed now
|
||||
- Adding label data to `card list --list` (uses a different API endpoint that doesn't include labels)
|
||||
- Changing the `card get` response (already returns card-level data via a different endpoint)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Add fields to Board struct rather than creating a separate BoardDetail type
|
||||
**Decision**: Add `Labels`, `CardLabels`, and `CardMemberships` as optional fields on the existing `Board` struct with `omitempty`.
|
||||
|
||||
**Alternative**: Create a `BoardDetail` struct for the enriched response. Rejected — the fields are simply absent when not fetched (e.g., `ListBoards`), and `omitempty` handles this cleanly without a type split.
|
||||
|
||||
### Add Labels field to CardWithList for card list --board output
|
||||
**Decision**: Add a `Labels` field (`[]string` of label names) to the `CardWithList` struct. `ListCardsByBoard` will resolve card→label associations via the `cardLabels` join table and `labels` list from the board response.
|
||||
|
||||
**Alternative**: Return full `Label` objects per card. Rejected — label names are sufficient for display and filtering; full objects add noise to output.
|
||||
|
||||
### Use join-table approach matching the API structure
|
||||
**Decision**: Keep `CardLabel` as the join entity (cardId + labelId), and resolve to label names at the point of use (in `ListCardsByBoard`). This mirrors the Planka API's relational structure.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[Additive JSON fields]** → `card list --board` output will include a new `labels` array per card. Existing consumers that don't use this field are unaffected. Scripts using strict JSON parsing may need updating.
|
||||
- **[Partial included parsing]** → We still skip users, taskLists, attachments, etc. This is intentional — parse only what's needed. Future changes can add more fields incrementally.
|
||||
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
The `GetBoard` API response includes `cardLabels`, `labels`, and `cardMemberships` in its `included` data, but `pcli` only parses `lists` and `cards` — silently discarding the rest. This means there is no way to determine which labels are attached to which cards without making per-card API calls. The kanban sync workflow needs to identify agent-labelled cards from a board listing, and currently must fall back to name-matching because label data is unavailable.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Expand the `Board` struct in `model/types.go` to include `Labels`, `CardLabels`, and `CardMemberships` fields
|
||||
- Update `GetBoard` in `client/boards.go` to parse these additional `included` fields from the API response
|
||||
- Update `ListCardsByBoard` to enrich card output with label names (via `cardLabels` join table + `labels`)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_None — this enhances existing capabilities._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `api-client`: `GetBoard` SHALL parse `labels`, `cardLabels`, and `cardMemberships` from the board response `included` data
|
||||
- `card-operations`: `ListCardsByBoard` (card list --board) SHALL include label names on each card
|
||||
|
||||
## Impact
|
||||
|
||||
- **Code**: `model/types.go` (Board struct), `client/boards.go` (GetBoard parsing), `client/cards.go` (ListCardsByBoard enrichment)
|
||||
- **API**: No new API calls — parsing data already returned by `GET /api/boards/:id`
|
||||
- **Breaking changes**: None — new fields are additive; JSON output gains new fields but existing fields unchanged
|
||||
@@ -0,0 +1,25 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Board operations
|
||||
The client SHALL provide a method to get a single board by ID (`GET /boards/{id}`), list board actions (`GET /boards/{boardId}/actions`) with pagination support, create a board (`POST /projects/{projectId}/boards`), and delete a board (`DELETE /boards/{id}`). `GetBoard` SHALL parse the `included` object from the response and populate the Board model with `lists`, `cards`, `labels`, `cardLabels`, and `cardMemberships`.
|
||||
|
||||
#### Scenario: Get board
|
||||
- **WHEN** `GetBoard` is called with a board ID
|
||||
- **THEN** the client SHALL send `GET /boards/{id}` and return a Board model including its included lists, cards, labels, cardLabels, and cardMemberships
|
||||
|
||||
#### Scenario: Get board includes labels
|
||||
- **WHEN** `GetBoard` is called and the board has labels defined
|
||||
- **THEN** the returned Board SHALL contain a `Labels` slice with all board labels
|
||||
|
||||
#### Scenario: Get board includes card-label associations
|
||||
- **WHEN** `GetBoard` is called and cards on the board have labels attached
|
||||
- **THEN** the returned Board SHALL contain a `CardLabels` slice with all card-label associations
|
||||
- **AND** each `CardLabel` entry SHALL contain `cardId` and `labelId` fields
|
||||
|
||||
#### Scenario: Get board includes card memberships
|
||||
- **WHEN** `GetBoard` is called and cards on the board have members assigned
|
||||
- **THEN** the returned Board SHALL contain a `CardMemberships` slice with all card-membership associations
|
||||
|
||||
#### Scenario: List board actions
|
||||
- **WHEN** `ListBoardActions` is called with a board ID and limit
|
||||
- **THEN** the client SHALL paginate through `GET /boards/{boardId}/actions` and return action items
|
||||
@@ -0,0 +1,23 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Enriched board-level card listing
|
||||
The system SHALL provide a `card list --board <id>` operation that returns all cards across all lists in a board, with each card enriched with the `listName` field and a `labels` field. The operation SHALL: (1) call `GET /boards/{id}` to retrieve the board and its included lists, cards, labels, and cardLabels, (2) build a card-to-label-names map by joining `cardLabels` with `labels`, (3) inject `listName` and `labels` into each card. The `labels` field SHALL be an array of label name strings. The `--limit` flag SHALL apply to the total number of cards returned across all lists.
|
||||
|
||||
#### Scenario: List all cards on a board
|
||||
- **WHEN** `pcli card list --board <id>` is executed
|
||||
- **THEN** the system SHALL return all cards from all lists in the board
|
||||
- **AND** each card SHALL include a `listName` field with the name of its containing list
|
||||
- **AND** each card SHALL include a `labels` field with an array of label names attached to that card
|
||||
|
||||
#### Scenario: Card with multiple labels
|
||||
- **WHEN** a card has two labels ("bug" and "urgent") attached
|
||||
- **THEN** the `labels` array for that card SHALL contain both "bug" and "urgent"
|
||||
|
||||
#### Scenario: Card with no labels
|
||||
- **WHEN** a card has no labels attached
|
||||
- **THEN** the `labels` array for that card SHALL be an empty array (not null)
|
||||
|
||||
#### Scenario: Board card listing with limit
|
||||
- **WHEN** `pcli card list --board <id> --limit 10` is executed
|
||||
- **THEN** the system SHALL return at most 10 cards total across all lists
|
||||
- **AND** each card SHALL include the `listName` and `labels` fields
|
||||
@@ -0,0 +1,15 @@
|
||||
## 1. Model Changes
|
||||
|
||||
- [x] 1.1 Add `Labels []Label`, `CardLabels []CardLabel`, and `CardMemberships []CardMembership` fields to `Board` struct in `model/types.go` (with `json:",omitempty"`)
|
||||
- [x] 1.2 Add `Labels []string` field to `CardWithList` struct in `model/types.go`
|
||||
|
||||
## 2. Client Changes
|
||||
|
||||
- [x] 2.1 Update `GetBoard` in `client/boards.go` to parse `labels`, `cardLabels`, and `cardMemberships` from `included` response
|
||||
- [x] 2.2 Update `ListCardsByBoard` in `client/cards.go` to build label-name map from board's `CardLabels` and `Labels`, and populate `Labels` on each `CardWithList`
|
||||
|
||||
## 3. Verification
|
||||
|
||||
- [x] 3.1 Build and test `pcli board get <id>` — verify JSON output includes labels and cardLabels when present
|
||||
- [x] 3.2 Test `pcli card list --board <id>` — verify each card includes a `labels` array
|
||||
- [x] 3.3 Test card with no labels returns empty array (not null)
|
||||
Reference in New Issue
Block a user