feat: clear the post-sweep TODO residue #32

Merged
David merged 1 commit from feat/todo-residue-cleanup into main 2026-05-14 19:31:55 +02:00
Owner

Summary

Three independent gaps from TODO.md shipped in one bundle. After this lands, the only remaining items are the yt update checksum sidecar (blocked on a CI-side .sha256 publish) and the manual-test coverage notes.

Custom-field $type discovery

update_state and update_estimation now try the common StateIssueCustomField / PeriodIssueCustomField types first and, only on HTTP 400, fall back to a discovery round-trip against GET /api/admin/projects/<id>/customFields and retry once with the project-specific $type (translated from <Kind>ProjectCustomField to <Kind>IssueCustomField). The happy path keeps its single POST. Discovery failure (non-admin token, missing field) preserves the original 400 so the user still sees the underlying error rather than a misleading "discovery failed". discover_issue_field_type is also exposed for future callers (issue create, issue update) that want the same treatment. Three new wiremock tests cover retry-success, retry-skipped-on-200, and 400-bubbled-when-discovery-empty.

--json parity for writes

Adds --json to issue set-state, issue set-estimation, issue comment, issue tag add/remove, issue link/unlink, issue sprint set, work add, and work delete. Each emits a pretty-printed envelope with issue, action, the action-specific payload, dry_run, and ok. The mutation still fires; --json only controls output formatting (verified by a new wiremock test for set-state). Combines cleanly with the existing --dry-run flag: --json --dry-run prints the envelope with "dry_run": true and skips the API call.

SecretString wrap for cfg.token

The Config struct still derives Debug (tracing, panics, dbg!), and the token field is now a SecretString so accidental {:?} formatting prints Secret([REDACTED ...]) instead of the actual token. Serde adapters preserve the on-disk YAML shape (plain string, unchanged file format). A Config::token() -> &str accessor keeps callsites readable without forcing every client_from_cfg helper to import ExposeSecret. Locked in by a Debug-redaction unit test.

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- --deny warnings
  • cargo test --all-targets (193 passed)
  • Manual: yt --json issue set-state ISSUE-1 Done prints a valid JSON envelope on stdout
  • Manual: yt --dry-run --json issue comment ISSUE-1 "hi" prints "dry_run": true and does not POST
  • Manual: on a project whose State field is MultiEnumIssueCustomField, yt issue set-state ISSUE-1 Done succeeds (discovery kicks in after the initial 400) where it previously failed
## Summary Three independent gaps from `TODO.md` shipped in one bundle. After this lands, the only remaining items are the `yt update` checksum sidecar (blocked on a CI-side `.sha256` publish) and the manual-test coverage notes. ### Custom-field `$type` discovery `update_state` and `update_estimation` now try the common `StateIssueCustomField` / `PeriodIssueCustomField` types first and, only on HTTP 400, fall back to a discovery round-trip against `GET /api/admin/projects/<id>/customFields` and retry once with the project-specific `$type` (translated from `<Kind>ProjectCustomField` to `<Kind>IssueCustomField`). The happy path keeps its single POST. Discovery failure (non-admin token, missing field) preserves the original 400 so the user still sees the underlying error rather than a misleading "discovery failed". `discover_issue_field_type` is also exposed for future callers (`issue create`, `issue update`) that want the same treatment. Three new wiremock tests cover retry-success, retry-skipped-on-200, and 400-bubbled-when-discovery-empty. ### `--json` parity for writes Adds `--json` to `issue set-state`, `issue set-estimation`, `issue comment`, `issue tag add/remove`, `issue link/unlink`, `issue sprint set`, `work add`, and `work delete`. Each emits a pretty-printed envelope with `issue`, `action`, the action-specific payload, `dry_run`, and `ok`. The mutation still fires; `--json` only controls output formatting (verified by a new wiremock test for `set-state`). Combines cleanly with the existing `--dry-run` flag: `--json --dry-run` prints the envelope with `"dry_run": true` and skips the API call. ### `SecretString` wrap for `cfg.token` The `Config` struct still derives `Debug` (tracing, panics, `dbg!`), and the token field is now a `SecretString` so accidental `{:?}` formatting prints `Secret([REDACTED ...])` instead of the actual token. Serde adapters preserve the on-disk YAML shape (plain string, unchanged file format). A `Config::token() -> &str` accessor keeps callsites readable without forcing every `client_from_cfg` helper to import `ExposeSecret`. Locked in by a Debug-redaction unit test. ## Test plan - [x] `cargo fmt --all` - [x] `cargo clippy --all-targets --all-features -- --deny warnings` - [x] `cargo test --all-targets` (193 passed) - [ ] Manual: `yt --json issue set-state ISSUE-1 Done` prints a valid JSON envelope on stdout - [ ] Manual: `yt --dry-run --json issue comment ISSUE-1 "hi"` prints `"dry_run": true` and does not POST - [ ] Manual: on a project whose State field is `MultiEnumIssueCustomField`, `yt issue set-state ISSUE-1 Done` succeeds (discovery kicks in after the initial 400) where it previously failed
feat: clear the post-sweep residue (discovery, --json, secrecy)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 14s
Create release / Create release from merged PR (pull_request) Has been skipped
c2221b0ef1
Three independent gaps from `TODO.md` shipped in one bundle.

Custom-field `$type` discovery. `update_state` and `update_estimation` now try the common `StateIssueCustomField` / `PeriodIssueCustomField` types first and, only on HTTP 400, fall back to a discovery round-trip against `GET /api/admin/projects/<id>/customFields` and retry once with the project-specific `$type` (translated from `<Kind>ProjectCustomField` to `<Kind>IssueCustomField`). The happy path keeps its single POST. Discovery failure (non-admin token, missing field) preserves the original 400 so the user still sees the underlying error rather than a misleading "discovery failed". `discover_issue_field_type` is also exposed for future callers (`issue create`, `issue update`) that want the same treatment. Three new wiremock tests cover retry-success, retry-skipped-on-200, and 400-bubbled-when-discovery-empty.

`--json` parity for writes. Adds `--json` to `issue set-state`, `issue set-estimation`, `issue comment`, `issue tag add/remove`, `issue link/unlink`, `issue sprint set`, `work add`, and `work delete`. Each emits a pretty-printed envelope with `issue`, `action`, the action-specific payload, `dry_run`, and `ok`. The mutation still fires; `--json` only controls output formatting (verified by a new wiremock test for `set-state`). Combines cleanly with the existing `--dry-run` flag: `--json --dry-run` prints the envelope with `"dry_run": true` and skips the API call.

`SecretString` wrap for `cfg.token`. The Config struct still derives `Debug` (tracing, panics, `dbg!`), and the token field is now a `SecretString` so accidental `{:?}` formatting prints `Secret([REDACTED ...])` instead of the actual token. Serde adapters preserve the on-disk YAML shape (plain string, unchanged file format). A `Config::token() -> &str` accessor keeps callsites readable without forcing every `client_from_cfg` helper to import `ExposeSecret`. Locked in by a Debug-redaction unit test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
David merged commit c1506e29b8 into main 2026-05-14 19:31:55 +02:00
David deleted branch feat/todo-residue-cleanup 2026-05-14 19:31:56 +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!32
No description provided.