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,38 @@
|
||||
## Context
|
||||
|
||||
The `pcli status` command fetches all boards and aggregates card counts per list. It has no filtering capability. The `board list` command already supports `--project <name>` filtering via a `resolveProjectNameToID()` helper in `cmd/board.go` that does case-insensitive name matching against all projects. The Board model already contains a `ProjectID` field.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Add `--project` flag to `pcli status` that filters boards by project name
|
||||
- Reuse the existing `resolveProjectNameToID()` pattern from `cmd/board.go`
|
||||
- Update help text and README to document the new flag
|
||||
|
||||
**Non-Goals:**
|
||||
- Filtering by project ID (only name-based, consistent with `board list`)
|
||||
- Multiple project filtering (single project only)
|
||||
- Caching project lookups across commands
|
||||
- Changing the status output structure or adding new fields
|
||||
|
||||
## Decisions
|
||||
|
||||
### Filter boards before fetching details
|
||||
**Decision**: Apply the project filter on the initial `ListBoards()` result, before calling `GetBoard()` for each board. This avoids unnecessary API calls for boards that will be filtered out.
|
||||
|
||||
**Alternative**: Filter after fetching all board details. Rejected because it wastes API calls and time for boards not in the target project.
|
||||
|
||||
### Reuse resolveProjectNameToID from board.go
|
||||
**Decision**: Call the existing `resolveProjectNameToID()` function directly — it is already exported within the `cmd` package.
|
||||
|
||||
**Alternative**: Extract to a shared utility. Rejected as premature — two call sites within the same package doesn't warrant a new abstraction.
|
||||
|
||||
### totalBoards reflects filtered count
|
||||
**Decision**: When `--project` is used, `totalBoards` in the output reflects only the matching boards, not all boards. This matches the semantics of "how many boards am I looking at."
|
||||
|
||||
**Alternative**: Show total and filtered counts separately. Rejected — adds complexity for minimal value.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[Extra API call]** → When `--project` is used, an additional `ListProjects()` call is made to resolve the name. This is lightweight and consistent with `board list --project`.
|
||||
- **[No partial match]** → Project name must match exactly (case-insensitive). Consistent with existing `board list` behavior. Users who misspell get a clear "project not found" error.
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
The `pcli status` command currently shows a summary of **all** boards across all projects. In instances with many projects, this produces noisy output and makes it difficult to focus on a specific project's boards. The `board list` command already supports `--project` filtering — the status command should offer the same capability for consistency and usability.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add an optional `--project` flag to `pcli status` that filters the status summary to boards belonging to the named project
|
||||
- When `--project` is provided, only boards matching that project are included; `totalBoards` reflects the filtered count
|
||||
- When `--project` is omitted, behavior is unchanged (all boards shown)
|
||||
- Update command help text (`Short`, `Long`) to mention the filtering capability
|
||||
- Update README status section with `--project` usage example
|
||||
- Reuse the existing `resolveProjectNameToID()` helper from `cmd/board.go`
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_None — this enhances existing capabilities._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `status-command`: Add optional `--project` flag for project-scoped filtering
|
||||
- `cli-commands`: Update status command documentation to include `--project` flag
|
||||
|
||||
## Impact
|
||||
|
||||
- **Code**: `cmd/status.go` (add flag, filter logic), `cmd/board.go` (no change — reuses existing helper)
|
||||
- **Documentation**: `README.md` status section, command help strings
|
||||
- **API**: No new API calls — uses existing `ListProjects()` for name resolution, same `ListBoards()` + `GetBoard()` flow
|
||||
- **Breaking changes**: None — flag is optional, default behavior preserved
|
||||
@@ -0,0 +1,20 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Status command
|
||||
The system SHALL provide a top-level `status` command registered directly on the root command (not as a subcommand of any resource group). `pcli status` SHALL take no positional arguments. The command SHALL accept an optional `--project <name>` flag (string) to filter boards by project name. The command SHALL fetch all boards via `ListBoards`, optionally filter by project, then fetch each board's details via `GetBoard` sequentially, aggregate card counts per list, and output the result via the standard `output.Print` mechanism respecting the global `--format` flag.
|
||||
|
||||
#### Scenario: Run status command
|
||||
- **WHEN** `pcli status` is executed
|
||||
- **THEN** the system SHALL output a summary of all boards with their lists and card counts
|
||||
|
||||
#### Scenario: Run status command with project filter
|
||||
- **WHEN** `pcli status --project "MyProject"` is executed
|
||||
- **THEN** the system SHALL output a summary of only boards belonging to "MyProject"
|
||||
|
||||
#### Scenario: Status command respects format flag
|
||||
- **WHEN** `pcli status --format table` is executed
|
||||
- **THEN** the output SHALL be in table format
|
||||
|
||||
#### Scenario: Status command default format
|
||||
- **WHEN** `pcli status` is executed without a `--format` flag
|
||||
- **THEN** the output SHALL be in JSON envelope format
|
||||
@@ -0,0 +1,49 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Status command project filtering
|
||||
The system SHALL accept an optional `--project <name>` flag on the `pcli status` command. When `--project` is provided, the system SHALL resolve the project name to a project ID using case-insensitive exact matching against all accessible projects. The system SHALL then filter the board list to include only boards whose `projectId` matches the resolved project ID. The `totalBoards` count in the output SHALL reflect the filtered board count. When `--project` is omitted, behavior SHALL be unchanged (all boards shown).
|
||||
|
||||
#### Scenario: Status filtered by project name
|
||||
- **WHEN** `pcli status --project "MyProject"` is executed and the project exists with 2 boards
|
||||
- **THEN** the output SHALL include only the 2 boards belonging to "MyProject"
|
||||
- **AND** `totalBoards` SHALL be 2
|
||||
|
||||
#### Scenario: Status filtered by project name case-insensitive
|
||||
- **WHEN** `pcli status --project "myproject"` is executed and a project named "MyProject" exists
|
||||
- **THEN** the output SHALL include boards belonging to "MyProject"
|
||||
|
||||
#### Scenario: Status with project filter and no matching boards
|
||||
- **WHEN** `pcli status --project "EmptyProject"` is executed and the project exists but has no boards
|
||||
- **THEN** the output SHALL show `totalBoards` as 0 and an empty boards array
|
||||
|
||||
#### Scenario: Status with project not found
|
||||
- **WHEN** `pcli status --project "NonExistent"` is executed and no project with that name exists
|
||||
- **THEN** the system SHALL output an error "project not found: NonExistent"
|
||||
- **AND** the system SHALL exit with code 1
|
||||
|
||||
#### Scenario: Status without project flag
|
||||
- **WHEN** `pcli status` is executed without `--project`
|
||||
- **THEN** the output SHALL include all boards across all projects (unchanged behavior)
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Status command summary output
|
||||
The system SHALL provide a top-level `pcli status` command that outputs a summary of all boards (or boards filtered by `--project`), their lists, and card counts. The summary SHALL include the total number of boards. For each board, the summary SHALL include the board name and a breakdown of each list within that board showing the list name, the number of open cards (where `isClosed` is false), and the number of closed cards (where `isClosed` is true). Empty lists SHALL be included in the output with 0 open and 0 closed cards.
|
||||
|
||||
#### Scenario: Status with multiple boards and lists
|
||||
- **WHEN** `pcli status` is executed and there are boards with lists containing cards
|
||||
- **THEN** the output SHALL include the total board count
|
||||
- **AND** each board SHALL list all its lists with open and closed card counts
|
||||
|
||||
#### Scenario: Status with empty lists
|
||||
- **WHEN** a board contains a list with no cards
|
||||
- **THEN** that list SHALL appear in the output with 0 open cards and 0 closed cards
|
||||
|
||||
#### Scenario: Status with no boards
|
||||
- **WHEN** `pcli status` is executed and there are no boards
|
||||
- **THEN** the output SHALL indicate 0 boards
|
||||
|
||||
#### Scenario: Status with closed cards
|
||||
- **WHEN** a list contains both open and closed cards
|
||||
- **THEN** the open card count SHALL exclude closed cards
|
||||
- **AND** the closed card count SHALL be shown separately
|
||||
@@ -0,0 +1,19 @@
|
||||
## 1. Core Implementation
|
||||
|
||||
- [x] 1.1 Add `--project` flag to `statusCmd` in `cmd/status.go`
|
||||
- [x] 1.2 Add project filtering logic: resolve name via `resolveProjectNameToID()`, filter boards by `ProjectID` before fetching details
|
||||
- [x] 1.3 Update `totalBoards` count to reflect filtered results
|
||||
|
||||
## 2. Command Help Text
|
||||
|
||||
- [x] 2.1 Update `Short` and `Long` descriptions on `statusCmd` to mention `--project` filtering
|
||||
|
||||
## 3. Documentation
|
||||
|
||||
- [x] 3.1 Update README.md status section with `--project` usage example
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Build and manually test `pcli status --project <name>` with an existing project
|
||||
- [x] 4.2 Test error case: `pcli status --project "nonexistent"` returns "project not found" error
|
||||
- [x] 4.3 Test default behavior: `pcli status` without `--project` still shows all boards
|
||||
@@ -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