feat: Tier 1 bundle - all functional-completeness gaps from TODO #28

Merged
David merged 2 commits from feat/tier-1-bundle into main 2026-05-14 18:57:02 +02:00
Owner

Summary

Tier 1 bundle: every functional-completeness gap from TODO.md shipped as a single PR, per your direction. After this lands the CLI covers ~95% of day-to-day YouTrack workflows.

10 verbs added + 1 extended (yt list).

New verbs

  • yt issue update <id> with --summary, --description, --state, --priority, --type, --assignee, --json. Top-level fields go through POST /api/issues/<id>; custom fields ride the YouTrack command language so we don't enumerate every $type.
  • yt issue delete <id> [-y] with interactive confirmation.
  • yt issue tag {add,remove} <id> <tag>.
  • yt issue link <id> --type <type> <target> and yt issue unlink ....
  • yt issue attachment {list,add,get,remove}. Multipart upload via reqwest::multipart; download follows the relative url field and writes to <output> or ./<name>. Sizes formatted human-friendly.
  • yt user {me,list,show}. me is a single GET against /api/users/me; list takes --query + --top; show looks up by login.
  • yt project {list,show,states}. states <SHORT> walks the project's custom-field bundle to surface valid State values, which prevents set-state Foo from silently 400-ing.
  • yt work list <id> (history with the internal id) and yt work delete <id> <work-id> [-y].
  • yt work check --date YYYY-MM-DD extends the today-only check to any UTC date.

Extended yt list

  • -q / --query <raw> passes a raw YouTrack search query and bypasses the structured filters. Clap's conflicts_with_all enforces the exclusion at parse time.
  • --top <N> and --skip <N> for explicit pagination.
  • --all loops 100-at-a-time via a new fetch_issues_all helper.

Plumbing

  • Client grows delete_ignore, get_bytes, and post_multipart_json (the latter takes a prebuilt reqwest::multipart::Form).
  • New models: User, WorkItemFull (WorkItem doesn't carry the id; needed for work delete), Attachment, BundleValue.
  • New api helpers (~10): delete_issue, update_issue, fetch_issues_page/fetch_issues_all, get_user_me/list_users/get_user, get_project/list_project_states, list_work_items_full/delete_work_item, list_attachments/upload_attachment/download_attachment/delete_attachment, apply_command_one.
  • Cargo.toml: reqwest adds the multipart + stream features.

Out of scope (intentionally)

  • Tier 1 items now done; Tier 2 is the next bundle. The TODO entry about --dry-run is partially shipped (the updater) and will be generalized in Tier 2.

Verification

  • cargo fmt --check clean.
  • cargo clippy --all-targets -- -D warnings clean.
  • cargo test --all-targets: 182 tests pass (4 new). Wiremock tests cover the api-helper HTTP shapes; the table-formatting + size-humanization tests are pure functions. The command-language mutations (update/tag/link/unlink) reuse the already-tested apply_command plumbing.

Test plan

  • yt issue update ABC-1 --summary "new" writes the new summary; --state "In Progress" flips state.
  • yt issue delete ABC-99 prompts; -y skips.
  • yt issue tag add ABC-1 wip, then yt issue tag remove ABC-1 wip.
  • yt issue link ABC-1 --type "relates" ABC-2; same with unlink.
  • yt issue attachment add ABC-1 ./file.png, then list, then get ABC-1 file.png.
  • yt user me, yt user list --query jdoe, yt user show jdoe.
  • yt project list, yt project states ABC (use the output as the set of valid set-state values).
  • yt work list ABC-1, yt work delete ABC-1 <work-id>.
  • yt work check --date 2026-05-13.
  • yt list -q "for:me #Unresolved" runs against the raw query; structured filters are not applied.
  • yt list --all --json | jq length matches the full backlog count.

Sequence

Part 2 of 4 from today's batch. Part 1 (TODO additions) is PR #27. Tier 2 and Tier 3 PRs are next.

## Summary Tier 1 bundle: every functional-completeness gap from `TODO.md` shipped as a single PR, per your direction. After this lands the CLI covers ~95% of day-to-day YouTrack workflows. 10 verbs added + 1 extended (`yt list`). ## New verbs - **`yt issue update <id>`** with `--summary`, `--description`, `--state`, `--priority`, `--type`, `--assignee`, `--json`. Top-level fields go through `POST /api/issues/<id>`; custom fields ride the YouTrack command language so we don't enumerate every `$type`. - **`yt issue delete <id> [-y]`** with interactive confirmation. - **`yt issue tag {add,remove} <id> <tag>`**. - **`yt issue link <id> --type <type> <target>`** and **`yt issue unlink ...`**. - **`yt issue attachment {list,add,get,remove}`**. Multipart upload via `reqwest::multipart`; download follows the relative `url` field and writes to `<output>` or `./<name>`. Sizes formatted human-friendly. - **`yt user {me,list,show}`**. `me` is a single GET against `/api/users/me`; `list` takes `--query` + `--top`; `show` looks up by login. - **`yt project {list,show,states}`**. `states <SHORT>` walks the project's custom-field bundle to surface valid State values, which prevents `set-state Foo` from silently 400-ing. - **`yt work list <id>`** (history with the internal id) and **`yt work delete <id> <work-id> [-y]`**. - **`yt work check --date YYYY-MM-DD`** extends the today-only check to any UTC date. ## Extended `yt list` - **`-q / --query <raw>`** passes a raw YouTrack search query and bypasses the structured filters. Clap's `conflicts_with_all` enforces the exclusion at parse time. - **`--top <N>`** and **`--skip <N>`** for explicit pagination. - **`--all`** loops 100-at-a-time via a new `fetch_issues_all` helper. ## Plumbing - `Client` grows `delete_ignore`, `get_bytes`, and `post_multipart_json` (the latter takes a prebuilt `reqwest::multipart::Form`). - New models: `User`, `WorkItemFull` (`WorkItem` doesn't carry the id; needed for `work delete`), `Attachment`, `BundleValue`. - New api helpers (~10): `delete_issue`, `update_issue`, `fetch_issues_page`/`fetch_issues_all`, `get_user_me`/`list_users`/`get_user`, `get_project`/`list_project_states`, `list_work_items_full`/`delete_work_item`, `list_attachments`/`upload_attachment`/`download_attachment`/`delete_attachment`, `apply_command_one`. - `Cargo.toml`: `reqwest` adds the `multipart` + `stream` features. ## Out of scope (intentionally) - Tier 1 items now done; Tier 2 is the next bundle. The TODO entry about `--dry-run` is partially shipped (the updater) and will be generalized in Tier 2. ## Verification - `cargo fmt --check` clean. - `cargo clippy --all-targets -- -D warnings` clean. - `cargo test --all-targets`: **182 tests pass** (4 new). Wiremock tests cover the api-helper HTTP shapes; the table-formatting + size-humanization tests are pure functions. The command-language mutations (`update`/`tag`/`link`/`unlink`) reuse the already-tested `apply_command` plumbing. ## Test plan - [ ] `yt issue update ABC-1 --summary "new"` writes the new summary; `--state "In Progress"` flips state. - [ ] `yt issue delete ABC-99` prompts; `-y` skips. - [ ] `yt issue tag add ABC-1 wip`, then `yt issue tag remove ABC-1 wip`. - [ ] `yt issue link ABC-1 --type "relates" ABC-2`; same with `unlink`. - [ ] `yt issue attachment add ABC-1 ./file.png`, then `list`, then `get ABC-1 file.png`. - [ ] `yt user me`, `yt user list --query jdoe`, `yt user show jdoe`. - [ ] `yt project list`, `yt project states ABC` (use the output as the set of valid `set-state` values). - [ ] `yt work list ABC-1`, `yt work delete ABC-1 <work-id>`. - [ ] `yt work check --date 2026-05-13`. - [ ] `yt list -q "for:me #Unresolved"` runs against the raw query; structured filters are not applied. - [ ] `yt list --all --json | jq length` matches the full backlog count. ## Sequence Part 2 of 4 from today's batch. Part 1 (TODO additions) is PR #27. Tier 2 and Tier 3 PRs are next.
feat: Tier 1 bundle - all functional-completeness gaps from TODO
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 38s
e7dcf4358b
Ships every Tier 1 verb identified in the post-audit backlog as a single bundled PR per direction. After this lands, the CLI covers ~95% of day-to-day YouTrack workflows: every gap that was a "user noticed something is missing" is closed.

## New verbs

- `yt issue update <id> [--summary] [--description] [--state] [--priority] [--type] [--assignee] [--json]`. Top-level fields (summary, description) go through `POST /api/issues/<id>`; custom fields use the YouTrack command language via `apply_command` so we don't have to enumerate every `$type`.
- `yt issue delete <id> [-y]`. `DELETE /api/issues/<id>`. Interactive confirmation unless `-y`.
- `yt issue tag {add,remove} <id> <tag>`. Wraps the command-language `tag <name>` / `remove tag <name>`.
- `yt issue link <id> --type <type> <target>` and `yt issue unlink <id> --type <type> <target>`. Same shape; the unlink path prefixes the command with `remove`.
- `yt issue attachment {list,add,get,remove}`. Multipart upload via `reqwest::multipart` (gated behind the new `multipart` + `stream` features on reqwest); download follows the relative `url` field YouTrack returns and writes to a configurable destination; remove uses `DELETE`. Sizes are formatted human-friendly (`512 B`, `2.00 KB`, `2.38 MB`).
- `yt user me / list / show <login>`. `me` is one GET against `/api/users/me`; `list` takes `--query` (substring; YouTrack server-side) and `--top` (default 50); `show` looks up by login.
- `yt project list / show <short> / states <short>`. `list` reuses the private `list_projects` helper that `issue create` already had; `states` walks the project's custom-field bundle to surface the valid State values - the same lookup that prevents `issue set-state Foo` from silently 400-ing.
- `yt work list <id>` returns the full work-item history with the internal id (needed to delete). `yt work delete <id> <work-id> [-y]` removes one.
- `yt work check --date YYYY-MM-DD` extends the existing today-only check to any UTC date.

## Extended `yt list`

- `-q / --query <raw>` passes a raw YouTrack search query through and bypasses the structured `--sprint / --assignee / --type` filters; clap's `conflicts_with_all` enforces the exclusion.
- `--top <N>` and `--skip <N>` for explicit pagination. Default behavior (neither) preserves the existing single-page request shape.
- `--all` loops 100-at-a-time via the new `fetch_issues_all` helper until a short page comes back.

## Plumbing

- `Client` grows `delete_ignore`, `get_bytes`, and `post_multipart_json`. `delete_ignore` mirrors `post_ignore` (errors on non-2xx, drains body); `get_bytes` returns raw bytes for attachment downloads (sends `Accept: */*` instead of `application/json`); `post_multipart_json` accepts a prebuilt `reqwest::multipart::Form`.
- `yt::models` adds `User`, `WorkItemFull` (carries the id field that `WorkItem` from inspect doesn't), `Attachment`, `BundleValue` (one entry in a project custom-field bundle).
- `yt::api` adds: `delete_issue`, `update_issue`, `fetch_issues_page` + `fetch_issues_all`, `get_user_me / list_users / get_user`, `get_project / list_project_states`, `list_work_items_full / delete_work_item`, `list_attachments / upload_attachment / download_attachment / delete_attachment`, `apply_command_one` (single-issue shorthand for `apply_command`).
- `Cargo.toml`: reqwest grows the `multipart` + `stream` features (needed for attachment upload).

## Verification

- `cargo fmt --check` clean.
- `cargo clippy --all-targets -- -D warnings` clean.
- `cargo test --all-targets`: 182 tests pass (4 new). The api-helper tests rely on wiremock for HTTP shape; the table-formatting + size-humanization tests are pure-function. Mutation paths through the commands API (`update`, `tag`, `link/unlink`) reuse existing `apply_command` plumbing which is already covered.

## Test plan

- [ ] `yt issue update ABC-1 --summary "new title"` writes the new summary; `--state "In Progress"` flips the state via the command API.
- [ ] `yt issue delete ABC-99` prompts; `-y` skips the prompt.
- [ ] `yt issue tag add ABC-1 wip`, then `yt issue tag remove ABC-1 wip`.
- [ ] `yt issue link ABC-1 --type "relates" ABC-2`; `yt issue unlink ABC-1 --type "relates" ABC-2`.
- [ ] `yt issue attachment add ABC-1 ./screenshot.png`, then `yt issue attachment list ABC-1`, then `yt issue attachment get ABC-1 screenshot.png`.
- [ ] `yt user me`, `yt user list --query jdoe`, `yt user show jdoe`.
- [ ] `yt project list`, `yt project states ABC` (catches mis-named states before `set-state` does).
- [ ] `yt work list ABC-1`, `yt work delete ABC-1 <work-id>`.
- [ ] `yt work check --date 2026-05-13`.
- [ ] `yt list --query "for:me #Unresolved"` returns whatever YouTrack matches against that raw query, ignoring the structured filters.
- [ ] `yt list --all --json | jq length` returns the full backlog (loops pages of 100 until short).
Merge branch 'main' into feat/tier-1-bundle
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + build + tests (pull_request) Successful in 21s
2dfac734e6
David merged commit 2a03d31d64 into main 2026-05-14 18:57:02 +02:00
David deleted branch feat/tier-1-bundle 2026-05-14 18:57:02 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/youtrack-cli!28
No description provided.