Table of Contents generated with DocToc
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.
uv (uv run --directory tools/github-body-field body-field …); stdlib-only, no third-party dependencies.uv; gh — the script shells out to it for all GitHub access (the --repo argument is forwarded verbatim).gh session (gh auth status must pass) — the body read / parse / replace / push all go through gh.api.github.com via gh.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.
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.
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.
The parser is a small state machine that:
^### <Name>$ lines as field headings;``` and ~~~) so a literal ### foo inside a shell snippet never false-matches as a heading;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.
| 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. |