feat: truncate --fields body uniformly (task 0021)
Rule that the `body` extra field truncates at 500 chars like `issue view`, resolving the spec's Principle 3 / Command Surface contradiction. Route the `body` extractor through a new `truncatedBody()` FieldDef so `issue list`, `issue create`, `pr list`, and `search` all present it exactly as the detail views do, and add a `--full` flag to each to suppress truncation, keeping the inline hint's "use --full" promise honest.
This commit was merged in pull request #21.
This commit is contained in:
@@ -15,5 +15,7 @@ Simpler, portable, consistent with the stated reference.
|
||||
## Consequences
|
||||
|
||||
`issue view` and `pr view` both accept `--full` to return untruncated body.
|
||||
`issue list`, `issue create`, `pr list`, and `search` also accept `--full`, since they offer `body` via `--fields` and that field truncates identically (task 0021).
|
||||
The flag keeps the inline hint's "use --full" promise honest on those commands too.
|
||||
`pr diff` truncates at 4000 chars (matching gh-axi's `DIFF_TRUNCATE_LIMIT`); when truncated, a next-step suggestion to rerun with `--full` is prepended.
|
||||
The `full_content` field name and temp-file design from the spec draft are dropped entirely.
|
||||
|
||||
@@ -98,7 +98,7 @@ This holds even when invoked by the SessionStart hook; error noise in non-Gitea
|
||||
Always passes `type=issues` — Gitea's issue endpoints also serve PRs, which must never appear in issue lists (see Issue/PR Type Guard).
|
||||
Client-side `--sort` reorders without changing membership, so the standard `count: N of T total` line is kept (see ADR 0005); full pagination still precedes sorting.
|
||||
Default output fields (matching gh-axi): `number`, `title`, `state` (lowercased), `author` (plucked from `user.login`), `created` (relative time).
|
||||
Extra fields via `--fields`: `body` (raw), `closedAt` (relative time, as `closed_at`), `labels` (joined names), `milestone` (title), `updatedAt` (relative time, as `updated_at`), `url`.
|
||||
Extra fields via `--fields`: `body` (truncated at 500, exactly as `issue view` presents it; `--full` shows it raw), `closedAt` (relative time, as `closed_at`), `labels` (joined names), `milestone` (title), `updatedAt` (relative time, as `updated_at`), `url`.
|
||||
No `type` field in output — Gitea has no issue types.
|
||||
|
||||
**`issue view <n> [flags]`**
|
||||
@@ -118,7 +118,7 @@ No sub-issue augmentation (Gitea does not model issue hierarchies; use `issue bl
|
||||
`--project` is excluded (Gitea has no projects REST API).
|
||||
`--type` is excluded (Gitea has no issue types).
|
||||
Output schema: `issue: { number, title, state, url }` (where `url` = `html_url`).
|
||||
Extra fields available via `--fields`: `labels`, `assignees`, `milestone`, `body`.
|
||||
Extra fields available via `--fields`: `labels`, `assignees`, `milestone`, `body` (truncated at 500, exactly as `issue view` presents it; `--full` shows it raw).
|
||||
|
||||
**`issue edit <n> [flags]`**
|
||||
`--title`;
|
||||
@@ -209,7 +209,7 @@ No gh-axi equivalent.
|
||||
`--fields <a,b,c>`.
|
||||
`--search` is explicitly forbidden (VALIDATION_ERROR; help: `` Use `gitea-axi search prs "<query>"` for full-text search ``).
|
||||
Default output fields (matching gh-axi): `number`, `title`, `state` (lowercased), `author` (plucked from `user.login`), `draft` (bool→yes/no), `review` (`reviewDecision` mapped: APPROVED→approved, CHANGES_REQUESTED→changes_requested, REVIEW_REQUIRED→required).
|
||||
Extra fields via `--fields`: `body` (raw), `createdAt` (relative time, as `created`), `labels` (joined names), `milestone` (title), `mergedAt` (relative time, as `merged_at`), `url`.
|
||||
Extra fields via `--fields`: `body` (truncated at 500, exactly as `pr view` presents it; `--full` shows it raw), `createdAt` (relative time, as `created`), `labels` (joined names), `milestone` (title), `mergedAt` (relative time, as `merged_at`), `url`.
|
||||
`reviewDecision` is computed client-side by fetching reviews for each PR in parallel (one extra HTTP call per PR; see ADR 0006).
|
||||
When any client-side filter is active, the count line shows `count: N of T total` with `T` computed from the in-memory filtered result set (see ADR 0005).
|
||||
|
||||
@@ -361,7 +361,7 @@ Full-text search within the current repository, added because `--search` on the
|
||||
The positional `<query>` is required (`VALIDATION_ERROR` if missing).
|
||||
Endpoint: `GET /repos/issues/search` with `q=<query>`, `type=issues` or `type=pulls`, and `owner=<owner>`.
|
||||
The endpoint has no repo-name filter, so results are additionally filtered client-side to the current repository via each result's `repository` field — the standard client-side filtering policy applies, including its `count: N of T total` rule with `T` from the filtered set.
|
||||
Flags: `--state <open|closed|all>` (default open); `--label <name>` (API-supported — comma-separated names); `--limit <n>` (default 30); `--fields <a,b,c>`.
|
||||
Flags: `--state <open|closed|all>` (default open); `--label <name>` (API-supported — comma-separated names); `--limit <n>` (default 30); `--fields <a,b,c>` (the same extras as `issue list` / `pr list`, so `body` is truncated at 500); `--full` (shows any `--fields body` raw, matching the list commands).
|
||||
Default output fields (both commands): `number`, `title`, `state`, `author`, `created` — a locator schema; search results are Issue-shaped for both types, and `draft`/`review` parity with `pr list` would require two extra fetches per result for a command whose job is finding the number to feed into `issue view` / `pr view`.
|
||||
Output blocks: `issues:` / `pull_requests:`, matching the list commands.
|
||||
Empty state: standard `<noun>[0]: (none)`.
|
||||
@@ -464,11 +464,15 @@ Field extraction uses a `FieldDef` type system with typed extractors: nested plu
|
||||
|
||||
**Principle 3 — Content truncation.**
|
||||
Body text is truncated at **500 characters** in all contexts (list and detail alike), matching gh-axi.
|
||||
"All contexts" is exhaustive: it includes the `body` field offered via `--fields` on `issue list`, `issue create`, `pr list`, and `search`, which truncate identically to `issue view` / `pr view` rather than emitting the body raw (task 0021).
|
||||
The Command Surface once described that field as "`body` (raw)".
|
||||
"Raw" is resolved here to mean *uncleaned markdown* (short bodies still pass through byte-for-byte, un-`cleanBody`-ed), never *unbounded* — an unbounded body on a list path would let `issue list --limit 30 --fields body` spill thirty full bodies into an agent's context, the exact cost this principle exists to prevent.
|
||||
Comment bodies truncate at 800 characters wherever they appear (comment-post output and `--comments` view blocks), with cleanBody applied.
|
||||
Diff content is truncated at 4000 characters.
|
||||
When body truncation occurs, a hint is appended inline: `"... (truncated, N chars total - use --full to see complete body)"`.
|
||||
When diff truncation occurs, `truncated: true` and `original_length: N` are added as separate fields, and a next-step suggestion to use `--full` is prepended.
|
||||
`--full` on `issue view` and `pr view` suppresses all truncation in the command's output (entity body and comment bodies alike); `--full` on `pr diff` suppresses diff truncation.
|
||||
`--full` on `issue list`, `issue create`, `pr list`, and `search` likewise suppresses the `--fields body` truncation, so the hint's "use --full to see complete body" holds on every command that offers the field.
|
||||
Before truncation, a `cleanBody` step is applied **only when the raw body exceeds the truncation limit**.
|
||||
`cleanBody` normalizes Gitea issue/PR URLs using the detected hostname (`https://<host>/<owner>/<repo>/issues/N` → `Issue#N`; `.../pulls/N` → `PR#N`), strips markdown image embeds, removes long URLs in markdown links and standalone text, and collapses email-style quoted blocks — matching gh-axi's transforms plus Gitea-specific URL normalization.
|
||||
If cleaning brings the body within the limit, the cleaned body is returned with an appended note; if it still exceeds the limit, the cleaned body is truncated.
|
||||
|
||||
@@ -23,7 +23,26 @@ The ruling is verified at the fixture/unit tier only: body truncation is determi
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] The spec no longer contradicts itself: Principle 3 and the Command Surface's `body` field agree, with the reasoning recorded
|
||||
- [ ] `issue list --fields body` and `issue create --fields body` behave identically under the ruling
|
||||
- [ ] If truncation wins, the truncation hint and `--full` affordance match how `issue view` already presents a truncated body (see ADR 0003)
|
||||
- [ ] Tests cover the ruled behaviour on both commands
|
||||
- [x] The spec no longer contradicts itself: Principle 3 and the Command Surface's `body` field agree, with the reasoning recorded
|
||||
- [x] `issue list --fields body` and `issue create --fields body` behave identically under the ruling
|
||||
- [x] If truncation wins, the truncation hint and `--full` affordance match how `issue view` already presents a truncated body (see ADR 0003)
|
||||
- [x] Tests cover the ruled behaviour on both commands
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
**The ruling: `--fields body` truncates.**
|
||||
Principle 3 already declared truncation applies "in all contexts (list and detail alike)"; the four `--fields body` extractors were the drift, not the intent.
|
||||
"Raw" in the Command Surface is resolved to mean *uncleaned markdown* (short bodies still pass through byte-for-byte), never *unbounded* — an unbounded body on a list path lets `issue list --limit 30 --fields body` spill thirty full bodies into an agent's context, the exact cost Principle 3 exists to prevent.
|
||||
|
||||
**Code.**
|
||||
Added a `truncatedBody()` `FieldDef` in `fields.ts` (alongside `pluck`/`joined`/`relativeTimeField`) that routes the value through the same `truncateBody(value, BODY_TRUNCATE_LIMIT, host)` that `issue view` / `pr view` use, so the hint text and `cleanBody`-on-overflow behaviour are byte-identical.
|
||||
`ExtractContext` gained `host` and `full` (both required) — the render context now declares its hostname and truncation mode explicitly; every `extractRow` call site was updated (the body-less ones — dashboard, label list, relationships — pass `full: false`).
|
||||
The four registries (`ISSUE_LIST`, `ISSUE_CREATE`, `PR_LIST`, `SEARCH` extra fields) now use `truncatedBody("body")`.
|
||||
|
||||
**Scope beyond the two commands the ACs name.**
|
||||
The task body mandated that "`issue list`, `issue create`, and any later command exposing `body` via `--fields` must behave the same", so `pr list` and `search` were included too — each got the ruling and a parity test.
|
||||
A `--full` flag (suppresses the `--fields body` truncation, matching `issue view`) was added to `issue list`, `issue create`, `pr list`, and `search`, since the inline hint literally says "use --full to see complete body" and that promise must be keepable on every command that offers the field.
|
||||
Help text for all four commands and ADR 0003's consequences were updated accordingly.
|
||||
|
||||
**Verification tier.**
|
||||
Fixture/unit tier only, as the task specified — no e2e case: truncation is deterministic string processing over a body the CLI already holds, with no live-Gitea response semantics to attest to.
|
||||
|
||||
Reference in New Issue
Block a user