/github-delivery-retro — contract¶
The skill lives at .agents/skills/github-delivery-retro/SKILL.md; the file under
.claude/skills/ is an adapter that points at it. This page is the contract: what
the skill guarantees, what it refuses, and how to tell when it is lying.
What it answers¶
Was this period fruitful, relative to the previous period of equal length? Not "how much happened" — how much of what happened moved the active milestone.
Read-only. It opens no PRs, edits no issues, and posts no comments.
The measurement stance¶
Five dimensions, defined in the skill: delivered outcomes, flow, quality, focus,
review effectiveness. Every figure is reported as this period → previous period,
because a bare number has no interpretation.
Four things are not measured, and the exclusion is the point:
| Excluded | Because it rewards |
|---|---|
| Commit count | Splitting work |
| Lines added or changed | Verbosity — and punishes deletion, which is often the delivery |
| Merged-PR count as a standalone score | Volume over outcome |
| Hours, sessions, days active | Nothing this skill has any business scoring |
Merged-PR count still appears, as an input, next to what those PRs served. The distinction between an input and a score is load-bearing: a founder merging ten small PRs is not more productive than one who closed a release gate, and the verdict rules in §7 of the skill make that fall out of the arithmetic rather than being asserted.
The one rule shared with the Linear skills¶
A merged implementation PR does not complete a release gate. The gate needs its
own acceptance evidence. /github-delivery-retro and /linear-release-audit must
never disagree about whether a gate was delivered; if they do, one of them has
accepted a PR merge as gate evidence and that is the bug.
Verdicts¶
| Verdict | Condition |
|---|---|
| Fruitful | ≥1 release-gate outcome completed with evidence, and the majority of merged work served the active milestone, and no unresolved rework from the window |
| Low-delivery | No gate outcome completed and under half of merged work served the active milestone |
| Mixed | Anything else — stated as which half is which, never averaged into a shrug |
| Unscoped | No milestone could be resolved for the window. The evidence is reported, the verdict is withheld, and the missing scope source is named |
Unscoped is not a fourth flavour of bad week — it is the refusal to score one. The
other three verdicts all divide by the milestone share, so a run that cannot establish
what the milestone was has no denominator and says so.
Scope is resolved as of the window, not as of today¶
The milestone comes from docs/product/mvp-scope.md as it stood at the window's
to, pinned through git history (git log --before=$TO … && git show $SHA:…), or
from an explicit caller-supplied --milestone, or the run is Unscoped. Linear is
not a fallback: projectMilestone is current state and carries no history, so any
Linear-derived milestone figure on a past window is labelled as read-now.
This matters most for windows that predate the scope contract itself
(mvp-scope.md first landed on main on 2026-08-07). Scoring one of those against
today's milestone silently re-attributes old work and produces a confident wrong
verdict — which is exactly the failure Unscoped exists to prevent.
On summary numbers¶
No single opaque productivity score, ever. If one summary figure is useful it is delivery confidence — how much of the active milestone this window's evidence supports — and it ships with its inputs visible so a reader can disagree with a component rather than with a verdict.
It is not a statement about a person. It is not comparable across people, it does not stand in for effort or hours, and it is not a performance metric. A week spent deleting a dependency, reading a source, or proving that a planned approach does not work can be the most valuable week of a release and will score low. When that happens, the report is required to say so.
How to tell when it is lying¶
The failure mode is a report that reads as improvement because activity rose. The
fixtures under .agents/skills/github-delivery-retro/fixtures/ are built to catch
exactly that: the current window merges 7 PRs against the previous window's
3, and must still come out Low-delivery, because it completed no gate, served
the milestone with 2 of 7 merges, and spent two of them reworking the prior week.
The fixture's milestone is caller-supplied, and the dry run only holds on that
condition. Those windows sit in July 2026, before mvp-scope.md existed, so the
git-pinned lookup finds nothing: a live run over the same dates without
--milestone "MVP-1 release gate" is correctly Unscoped, not Low-delivery.
The fixtures carry the milestone inline precisely so the dry run tests the verdict
arithmetic rather than the scope lookup. Reading the fixture's Low-delivery as
unconditional is the one way to mistake it.
Three further traps are built into the fixture data, each catching a rule prose cannot
enforce: PR #219 merges at exactly the window's exclusive to and must be
excluded under [from, to); the ci.runs counts deliberately disagree with the
per-merge ci_at_merge values so that using them as the CI denominator fails; and
PR #216's checks complete after its mergedAt, so it belongs in the
merged before checks completed bucket rather than the green one.
That third bucket exists because statusCheckRollup is check state at read time,
not at merge. A PR that merged before its checks finished looks identical to one that
waited, unless every check is filtered to completedAt ≤ mergedAt first — so
"CI green at merge" is reported as three buckets, green / red / merged-before-checks,
and the last is never folded into the first.
Run the fixture dry run after any edit to the skill. If the verdict comes back
Fruitful or Mixed, the edit broke it — and if it comes back Low-delivery while the
report claims the scope was git-pinned, the scope label is wrong even though the
verdict is right.