Repository navigation
Description
Problem
packages/agents/source/skills/pr/shared/scripts/validate-description.sh (506 lines on main, the second largest maintained skill script) checks a pull request description against the template before a PR is created or edited. It is called by pr-agent.sh, by review.sh and by the write-pr-description skill in both its Claude and Codex forms, and it sits in front of every agent-created pull request (feature 12 of #588). A shell implementation of this size is hard to test and slow in Bats, while its verdicts decide whether a description is accepted. Its verdict depends on more than the description text: it reads the repository configuration, the template, environment variables, the Git history and the base branch.
Acceptance criteria
- AC1: Given a description that meets the template, when it is validated in a repository, then nothing is printed and the exit status is 0.
- AC2: Given a description with a missing, misordered or empty required section, then the validator prints a finding that names the section and rule, with the same rule name, line number and message as today, and exits 1.
- AC3: Given any other rule violation that the validator reports today (frontmatter missing, unterminated or missing a key, empty labels, wrong owner-review label, unknown base, base equal to head, a path that does not exist, and the rest of the rule set), then the same finding is reported with the same rule name, line number and message.
- AC4: Given the recorded corpus of descriptions and contexts, then no description that the shell validator rejects is accepted by the Go validator, and every verdict equals the shell validator's.
- AC5: Given a draft that is missing or empty, or a template that is missing or has no sections, then the validator fails with the same fatal rule as today and never accepts; given a draft that exists and is non-empty but cannot be read, then it exits with the same non-zero status and the same standard output and standard error as the shell validator, and never accepts.
- AC6: Given the callers in the pr and write-pr-description skills of both the Claude and Codex packages, then each keeps its arguments, exit statuses and output streams.
- AC7: After regeneration, the validator entry points of both skill packages and both local deployments are generated from the canonical sources and agree with them.
- AC8: Non-functional: validating a 1,000-line description, measured from the start of the registered script path to exit in a fixture repository, takes under 300 ms at the median over 50 runs on the CI runner.
- AC9: Given a template chosen through the
PR_TEMPLATEenvironment variable, through the repository configuration'spr_template, or by default, and title bounds taken fromPR_TITLE_MIN_LENGTHandPR_TITLE_MAX_LENGTH, then the template and bounds used are the ones the shell validator picks, including its precedence, its refusal of an absolute path or a..path, and its reading of null,~and an empty string as absent. - AC10: Given a repository where the base is known by a remote-tracking branch, only by a local branch, or not at all, and where the commits since the base carry different landing types, then the base chosen and the title type required are the ones the shell validator picks.
- AC11: Given any set of findings, then they are printed in the shell validator's order, one per line as
<draft>:<line> [error] <rule> <message>on the same stream, followed by theTOTAL: <n> finding(s) in <draft>line. - AC12: Given a failing Git subprocess (not a repository, an unresolvable base, an unreadable history), then the validator's verdict and finding are the ones the shell validator gives.
Scope and non-goals
In scope: the description validator, its forwarding shim, the Claude and Codex entry points and their tests.
Non-goal: pull request creation, review signature and property resolution (create-pr.sh, a later wave) and the guard-gh hook (#719).
Non-goal: changing the template or any rule, or fixing a quirk of the shell validator; a quirk is recorded and preserved, and a separate issue may change it.
Non-goal: the shared readers in description.sh beyond what the validator calls.
Out of scope: the Markdown prose linters.
Approach
- Build the characterization matrix first, from the shell validator at a pinned commit of main, as committed fixtures of draft text, repository state and environment together with the exact standard output, standard error and exit status. The matrix covers every rule the validator can report at least once, and these contexts: template selection (environment, repository configuration, default, absolute and
..paths), title bounds, the scalar forms (null,~, empty), a remote-tracking base, a local-only base, an unknown base, the head equal to the base, commits with each landing type, a missing draft, an empty draft, an unreadable draft (recorded with its exit status and streams, because the read failure leaves through shell error handling and not through the fatal rule), a missing template, a template without sections, a failing Git subprocess, and the owner-review label (the repository configuration'sowner_review_labeland its default, case-insensitive matching against the draft's labels, an empty configured value, and a failing configuration read). Run the shell validator over it to confirm the recording. - Port the rules to Go one group at a time, each with a table test over its slice of the matrix, then require identical results over the whole matrix.
- Keep the script path as a forwarding shim of at most 10 lines that execs the Go command and fails with the usual fatal output if the binary is missing, so the existing callers keep their arguments. Delete the shell validator. Regenerate both skill variants from the canonical sources.
- The validator script is not matched by the golden or owner path patterns, so this change adds no protection change. The callers are switched only after the epic's rollout hold below.
- Deliver in the documented order:
mise run generate-skills, thenmise run skill-tokens, thenapm install, committing what they rewrite. Add todocs/agent-skills-migration.mdthat the pr skills need the release carrying the validator installed before the skills are deployed, or state "No consumer impact" with the reason.
Dependencies
Native blocked-by links: #333 (the Go binaries ship as release assets) and #544 (consumers install those assets, because the shim runs in consumer repositories). The change follows the sequence of #588: the binary is installed before the callers are switched, and features 3 to 8 (gates and the merge path) come first.
Blocked on: #720 (feature 3, the check judge) and #713 (feature 4, gate execution), held as prose lines and not native links while #720 is being split. Features 5 to 8 have no issues yet, so the rollout hold is: the shim is not switched until features 3 to 8 have shipped, and their issues are added as native blockers when they exist.
Independent of those features, and startable once #333 and #544 allow: the characterization matrix, the Go port and its tests. Only the switch of the callers waits.
Size
S. One PR: the Go validator, the recorded matrix, the shim and the generated outputs. The matrix is committed fixtures and not hand-written tests, which keeps the review size within S. If the port turns out to need more than one PR, this issue becomes an epic and is split into sub-issues by rule group before coding, each refined on its own.
Verify plan
- AC1: Go test over a clean description in the fixture repository, asserting empty output and exit 0, plus the accepted-description cases of
tests/skills/pr/scripts/pr-workflow.bats. - AC2: Go table tests for missing, misordered and empty sections, comparing rule, line and message with the recorded results.
- AC3: golden diagnostics over every other rule in the matrix, compared with the recorded shell results.
- AC4: differential test over the matrix asserting equal verdicts, and a check that no recorded rejected case becomes accepted. The claim covers the recorded matrix only; each new quirk found later is added to it.
- AC5: Go tests with a missing and an empty draft, a missing template and a template without sections, asserting the same fatal rule and a non-zero exit; and an unreadable draft, asserting the recorded exit status and streams.
- AC6: the pr-workflow Bats suite for the Claude package, plus the same cases run against the Codex package's copy of the entry point, asserting identical arguments, exit statuses and streams; the harness helper currently selects only the Claude package, so the Codex run is added explicitly.
- AC7:
mise run generate-skills,mise run skill-tokensandapm installin that order leave no diff,mise run check-generated-skillspasses, the shim has at most 10 lines, a search shows the shell validator is gone from the source, and the Claude and Codex outputs are byte-identical where the generator makes them so. - AC8: a process-level timing script that runs 50 invocations of the registered script path in the fixture repository with a 1,000-line description and reports the median, run in CI.
- AC9: matrix cases for each template source and each precedence combination, the refused paths, and the scalar forms.
- AC3 (owner-review label): matrix cases for the default label, a configured label, a differing case, an empty configured value and a failing configuration read, compared with the recorded results.
- AC10: matrix cases with a remote-tracking base, a local-only base, an unknown base, and each landing type among the commits.
- AC11: matrix cases with several findings, comparing the order, line numbers, streams and the TOTAL line, and a clean case asserting silence.
- AC12: matrix cases that run the validator outside a repository, with an unresolvable base and with an unreadable history.
- Delivery:
mise run checkis green on the exact head, and so aremise run cover-go-checkandmise run cover-py-check; the pull request lists every AC ID with its evidence.
Risks
Accepting a description the old validator would reject lets a malformed PR through; AC4 is the differential check for that, bounded by the matrix. A context that the matrix misses could change a verdict; AC9 to AC12 list the contexts the validator reads. The size could overrun one PR, handled by the split rule. The undo is reverting the PR, which restores the shell validator and its generated copies.
Operator authority
None
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Work type
Projects
- StatusShow more project fieldsBacklog
Activity
[-]Port the draft prose linter to Go and drop its duplicate[/-][+]Port the pull request description validator to Go[/+]tbhb-agent commentedon Oct 25, 2026
Refinement: NOT READY — compatibility evidence and rollout prerequisites need refinement
Checked
Gaps
Acceptance criteria: AC6 combines compatibility, shim layout, line count, and implementation deletion; AC7 combines generation and shared implementation. Move implementation details into Approach and give each AC one observable outcome. AC4’s universal “never accepted” claim exceeds what a finite corpus can establish; define its compatibility boundary.
Approach and compatibility evidence: “Full fixture corpus” is undefined. The current validator depends on repository configuration, template precedence, scalar parsing, remote-versus-local base selection, Git history, and filesystem state—not description text alone. See
packages/agents/source/skills/pr/shared/scripts/description.sh:23,packages/agents/source/skills/pr/shared/scripts/description.sh:93, andpackages/agents/source/skills/pr/shared/scripts/description.sh:102. Specify a characterization matrix covering every rule and these contextual inputs, including subprocess failures. Preserve diagnostic order, line numbers, streams, totals, and silence on success, rather than wording and verdict alone.Dependencies: Native blocked-by links are empty, although switching the shim requires binary availability through still-open Ship the Go binaries as release assets installed through mise's github backend #333. Add the corresponding native blocker or explicitly separate preparatory work from a blocked switch. Also account for Replace complex shell with tested Go, starting with gates and merging #588’s owner-approved protection of replacement Go enforcement paths before rollout; Port the pull request description validator to Go #718 currently omits that prerequisite.
Size: M is an allowed size, but Replace complex shell with tested Go, starting with gates and merging #588 explicitly requires larger Features to be divided into XS/S implementation PRs before coding. Port the pull request description validator to Go #718 instead defers splitting until the corpus comparison overruns. Resolve that conflict before implementation.
Verify plan: Every AC ID appears, but several checks do not prove their claims:
tests/skills/pr/test_helper.bash:9; name how the Codex entry point is exercised.Split
Resolve the epic’s sizing requirement now. A characterization/Go implementation slice followed by the compatibility switch and shell removal is a possible split. Intermediate work must keep deployed callers operational and avoid changing the shared publisher readers outside scope.
Definition of done
Unchecked items below are unmet by the current AC/Verify plan or pending implementation evidence.
mise run check,mise run cover-go-check, andmise run cover-py-check.Round: 1 of 3
tbhb-agent commentedon Oct 25, 2026
Refinement: NOT READY — unreadable-input contract and contextual coverage still need correction
Checked
tests/skills/pr/test_helper.bash:9.Gaps
AC5 requires a fatal rule that unreadable drafts do not produce. The shell checks existence and size, but not readability, at
packages/agents/source/skills/pr/shared/scripts/validate-description.sh:76. Reading occurs through the redirection atpackages/agents/source/skills/pr/shared/scripts/validate-description.sh:242; a read failure exits through shell error handling, without invokingfatal. Consequently, the AC5 verification cannot assert “the same fatal rule” for this case while preserving behavior. Require the recorded exit status and streams for unreadable drafts, reserving fatal-rule assertions for cases that actually produce them.The characterization matrix still omits configuration inputs that change the result. It covers
pr_template, but not the configurable reserved owner-review label or configuration-read failures. The validator reads and compares that label case-insensitively atpackages/agents/source/skills/pr/shared/scripts/validate-description.sh:280. Its reader supplies a default and rejects an explicitly empty value atpackages/agents/source/lib/agent.sh:40; configuration parsing can also reject invalid or duplicate settings atpackages/agents/source/lib/agent.sh:15. Add matrix cases for the default and overridden label, case-insensitive matching, an empty configured label and a failing configuration read, recording both streams and status. Otherwise, the stated matrix can pass while these contextual verdicts change.Existing sequencing prerequisites are missing from native blockers. Dependencies explicitly requires Replace complex shell with tested Go, starting with gates and merging #588 features 3–8 first, but links only Ship the Go binaries as release assets installed through mise's github backend #333 and feat(distribution): migrate consumer tool pins to release assets #544. Feature 3 already has Settle the check judge contract and divergence corpus before porting it #720, and feature 4 already has Port the gate runner and pass cache from Python to Go #713. Add those native blockers, or explicitly distinguish preparatory work that may proceed independently from the blocked switch. The hold for features 5–8 whose issues do not yet exist is clear.
Split
S is an acceptable declared size for one PR. The explicit conversion to an epic and XS/S sub-issues before coding if it exceeds one PR addresses #588’s split rule. No additional split is required at refinement.
Definition of done
The definition of done for #718 (Feature). Ticks show what the issue's plan already covers, not delivery evidence.
provenance:agent-assisted.Status:comment.mise run checkis green, and so are the coverage floorsmise run cover-go-checkandmise run cover-py-check, whichcheckdoesn't run.packages/agents/source/commits whatmise run generate-skills, thenmise run skill-tokens, thenapm installrewrite, in that order, andcheck-generated-skills(part oflint) passes.docs/agent-skills-migration.mdor the adoption docs, or the PR's Verification says "No consumer impact" and why.Round: 2 of 3
tbhb-agent commentedon Oct 25, 2026
Refinement: READY — compatibility contract and rollout boundaries are sufficiently defined
Checked
packages/agents/source/skills/pr/shared/scripts/description.sh:23,packages/agents/source/skills/pr/shared/scripts/description.sh:61,packages/agents/source/skills/pr/shared/scripts/description.sh:93andpackages/agents/source/skills/pr/shared/scripts/description.sh:102.packages/agents/source/skills/pr/shared/scripts/validate-description.sh:76andpackages/agents/source/skills/pr/shared/scripts/validate-description.sh:242.packages/agents/source/skills/pr/shared/scripts/validate-description.sh:280andpackages/agents/source/lib/agent.sh:15.tests/skills/pr/test_helper.bash:9. The Codex writer invokes the validator atpackages/agents/source/skills/write-pr-description/SKILL.codex.md:30.Gaps
None that require an implementer to guess or prevent the planned checks from proving their ACs. All three round-2 gaps are resolved.
Split
S is acceptable for the declared single PR. The explicit conversion to an epic and refined sub-issues before coding if it exceeds one PR satisfies the split boundary. No additional refinement split is required.
Definition of done
Ticks indicate coverage in the issue’s plan, not completed delivery evidence.
provenance:agent-assisted.Status:comment.mise run check,mise run cover-go-checkandmise run cover-py-check.Round: 3 of 3
tbhb-agent commentedon Oct 26, 2026
Refinement amendment for the shell-to-Go epic (#588), covering compatibility and protection. The acceptance criteria stay as written, except where noted.