feat: add issue blocks and blocked-by (task 0007)
All checks were successful
CI / test (pull_request) Successful in 35s
CI / test (push) Successful in 35s

This commit was merged in pull request #10.
This commit is contained in:
2026-07-13 19:41:04 -04:00
parent 16d2f29f19
commit 6c15e6082f
4 changed files with 590 additions and 16 deletions

View File

@@ -13,8 +13,22 @@ No gh-axi equivalent exists — the interface shape follows this spec alone.
## Acceptance criteria
- [ ] `issue blocks list <n>` and `issue blocked-by list <n>` render their respective output blocks with count lines and explicit empty states
- [ ] `issue blocks add <n> <target>` outputs `blocks: { issue: n, blocks: target }`; `issue blocked-by add <n> <blocker>` outputs `blocked_by: { issue: n, blocked_by: blocker }`
- [ ] Adding an existing relationship returns `already: true` (fetch-first check, no duplicate POST); removing a nonexistent relationship exits 0 silently-successfully
- [ ] Self-reference and cycle rejections from Gitea surface as `VALIDATION_ERROR` (exit 2) with the server's message
- [ ] Fixture-server tests cover list, add, idempotent re-add, remove, idempotent re-remove, and a 422 cycle rejection for both groups
- [x] `issue blocks list <n>` and `issue blocked-by list <n>` render their respective output blocks with count lines and explicit empty states
- [x] `issue blocks add <n> <target>` outputs `blocks: { issue: n, blocks: target }`; `issue blocked-by add <n> <blocker>` outputs `blocked_by: { issue: n, blocked_by: blocker }`
- [x] Adding an existing relationship returns `already: true` (fetch-first check, no duplicate POST); removing a nonexistent relationship exits 0 silently-successfully
- [x] Self-reference and cycle rejections from Gitea surface as `VALIDATION_ERROR` (exit 2) with the server's message
- [x] Fixture-server tests cover list, add, idempotent re-add, remove, idempotent re-remove, and a 422 cycle rejection for both groups
## Implementation Notes
Both groups are one config-parameterised implementation (`DependencyGroup`): `blocks` over `/issues/{index}/blocks`, `blocked-by` over `/issues/{index}/dependencies`, differing only in endpoint calls and the spec-fixed output names (`blocked_issues`/`blocking_issues`, `blocks`/`blocked_by`).
Decisions made mid-implementation, where the spec was silent:
- **`remove` output.** The spec fixes the `add` output shape but not `remove`'s. A successful deletion reports `<noun>: { issue, <target>, removed: true }`; a no-op removal (relationship already absent) reports `already: true` instead, mirroring `add`'s no-op and honouring the action/entity-block convention (a no-op reports the already-reached state rather than claiming an action it did not perform). Both `add` and `remove` are fetch-first against the fully paginated current set, so a nonexistent issue surfaces as `ISSUE_NOT_FOUND` before any mutation.
- **`list` row fields.** Rendered as `number`, `title`, `state` — the identifying essentials; the spec did not fix a row shape.
Review follow-ups addressed in this change:
- Extracted `parseIssueNumber` into `flags.ts` so the two-positional dependency parser and the existing `parsePositionalNumber` share one positive-integer rule and message (was duplicated).
- Added test coverage for the `ISSUE_NOT_FOUND` path (issue itself absent), which the code comments claim but nothing exercised.