blob: 85bdc31ea04de73eaa26a51b1cda82b8f8e6c32a [file] [view]
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [github-body-field](#github-body-field)
- [Prerequisites](#prerequisites)
- [Configuration](#configuration)
- [Why](#why)
- [Invocation](#invocation)
- [`get <issue> --field "<name>"`](#get-issue---field-name)
- [`set <issue> --field "<name>" --value "<v>" | --value-file <path>`](#set-issue---field-name---value-v----value-file-path)
- [`list <issue>`](#list-issue)
- [Body format assumptions](#body-format-assumptions)
- [Failure modes](#failure-modes)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
# github-body-field
**Capability:** contract:tracker
**Kind:** implementation
**Vendor:** GitHub
Read or rewrite a single `### Field` section of a GitHub issue
body **without bringing the body into agent context**.
## Prerequisites
- **Runtime:** Python 3.11+ run via `uv` (`uv run --directory tools/github-body-field body-field …`); stdlib-only, no third-party dependencies.
- **CLIs:** `uv`; `gh` — the script shells out to it for all GitHub access (the `--repo` argument is forwarded verbatim).
- **Credentials / auth:** an authenticated `gh` session (`gh auth status` must pass) — the body read / parse / replace / push all go through `gh`.
- **Network:** `api.github.com` via `gh`.
## Configuration
This helper has no persistent config file of its own. Callers pass
`--repo <tracker>` from `<project-config>/project.md` and the field names
declared by the adopter's tracker configuration, such as
`<project-config>/issue-tracker-config.md` or sibling
`*-config.md` files that define tracker body fields.
## Why
Tracker issues in `<tracker>` carry a structured body — a list of
`### <FieldName>` sections (e.g. *CVE tool link*, *Reporter
credited as*, *Public advisory URL*, *Short public summary for
publish*). The sync workflow PATCHes one of these fields at a time;
the legacy recipe was *read the full 10–15 KB body into agent
context, regex-edit one field, write it back*. That spent ~5 K
tokens per single-field flip, in addition to looping
reporter-supplied content (often the most sensitive content on
the tracker) through the agent for no reason.
This tool does the read / parse / replace / push in a subprocess.
Only the diff summary lands on the agent's stdout. The body never
crosses the boundary.
## Invocation
```bash
uv run --directory tools/github-body-field body-field --repo <owner>/<repo> <subcommand> ...
```
The `--repo` argument is forwarded verbatim to `gh`; omit it when
the current working directory is already inside the right clone.
### `get <issue> --field "<name>"`
Print the field's value to stdout (with a trailing newline added if
the value did not already end with one — convenient for shell
pipelines). Exit 3 if the field is absent or appears more than once.
### `set <issue> --field "<name>" --value "<v>" | --value-file <path>`
Replace the field's value in place. Either `--value` (single argv
string) or `--value-file` (any path; `-` reads stdin) must be
given. The replacement preserves the original heading line
byte-exact and re-uses the spacer-blank-line convention the original
section had.
`--dry-run` prints the diff summary to stderr but skips the push.
Exit 0 when written (or when the new value matched the old, in
which case stderr says `unchanged: ...` and no API call happens);
exit 3 when the field is absent or duplicated.
### `list <issue>`
Print every field heading present in the body, one per line, in
document order. With `--json`, emit a JSON array instead — useful
when an orchestrator wants to programmatically check what fields a
legacy tracker already has populated.
## Body format assumptions
The parser is a small state machine that:
- treats only top-level `^### <Name>$` lines as field headings;
- tracks fenced code blocks (`` ``` `` and `~~~`) so a literal
`### foo` inside a shell snippet never false-matches as a
heading;
- preserves the original body byte-exact when no change is needed
(idempotent rewrite — a `set` of the same value triggers no
API write).
If the body does not use the `### <FieldName>` convention at all
(legacy trackers from before the issue template was standardised),
`set` will report `field not found` and refuse to mutate. Fix the
tracker body first or fall through to the original "edit by hand"
path; this tool intentionally does not invent headings.
## Failure modes
| Exit | Meaning |
|---|---|
| 0 | Success (or `set` was a no-op because new value matched current). |
| 2 | CLI argument error (e.g. both `--value` and `--value-file` given). |
| 3 | Field heading not found, or matched more than once. The body is untouched. |
| other | `gh` returned non-zero; the underlying `gh` stderr is forwarded. |