Produce one cohesive user-story narrative for the current branch. Output goes to tmp/pr-story.md. Do not summarize or report to the user after writing — they review the file directly.
Phase 1: Examine changes
- Diff the branch against its base (committed + unstaged):
git diff "$(git merge-base HEAD origin/main)"...HEADandgit status --shortif needed. - Identify one primary user path to tell (e.g. send a single message). Do not weave parallel flows (blasts, API-only paths, verifications, etc.) into the story unless the user names a different path — save them for Notes instead.
- Trace that path in code: follow calls in execution order, noting only files touched on this branch.
Phase 2: Outline
Before writing prose, sketch a short outline (mental or scratch — not delivered to the user):
- Opening: who acts, where in the product, what triggers the flow
- Middle: call chain in order, branch points (
ifpaths for this invocation) - End: success path and failure path the user sees
- Notes (sketch only): parallel entry points, shared infra, migrations, and flags worth a reviewer mention
One line per step; anchor each step to a Module.function (direct parent only).
Phase 3: Write the story
Write to **tmp/pr-story.md** (repo-root tmp/, see Title and headings below).
Title and headings
- One story title only, as the first line:
## User story: <short description of the path>(e.g.## User story: sending a message through the embedded composer at the monthly limit). - Always use
##for that title — never#. The description after the colon is specific to this branch's primary path. - The Notes block uses
## Notesas its section heading (also##, not#). - When publishing to the PR, paste the file contents as-is (or append after existing body with a blank line). The file's
## User story: …line is the only story heading on the PR — do not inject anything else.
Voice and perspective
- Active plural for engineering work: we added, we call, we run.
- Change markers on what shipped: now, the new, we added — declarative, not scoped to "this send" unless describing the code change itself.
- Runtime behavior stays tied to this one story: if (not when) for branches; their platform, the user, the send where the path is specific to this invocation.
- Do not over-scope declarations: ❌ "For this send, that function now preloads…" → ✅ "That function now preloads…"
- Do not over-scope the change itself: ❌ "…the new monthly capacity check for this attempt" → ✅ "…the new monthly capacity check"
References
- Format:
Module.function— no arity (Send.send_message, not/4). - Direct parent only — short module name, not full lineage:
- ✅
ComposerComponent.handle_event,Messages.send_message,Send.send_message - ❌
SurgeWeb.Embedded.ComposerComponent.handle_event
- ✅
- Introduce a reference once as an anchor (often in parentheses), then refer indirectly while the narrative stays in that function: the handler, that function, it, the multi.
- Re-anchor by name when you return to a function after the story has moved through other functions in between. Do not rely on the handler, that function, back there, or it if another
Module.functionhas been discussed since the last beat in that function.- ✅ Back in
Send.send_message, if the debit passes…* - ✅
Send.send_messagereturns{:error, :monthly_outbound_message_limit}, which bubbles up tohandle_event("send", ...)inComposerComponent* - ❌ If the debit passes, the multi commits… (after paragraphs on
Structure,PlatformBillingInformation,RateLimiter) - ❌ …to the handler (when
ComposerComponent.handle_eventwas last named many beats ago)
- ✅ Back in
- Weave references with parentheticals so prose stays conversational — engineer explaining to engineer.
Structure
- No bullet lists in the story body — one paragraph or sentence per beat, blank lines between beats.
- Follow execution order through the primary path.
- Separate what we changed (declarative) from what happens on this attempt (
ifbranches, outcomes).
Content boundaries
- Do not mention tests anywhere — not in the story, not in Notes. No test files, no Hammox/Mox stubs, no test plans, no "how to verify" checklists.
- Do not catalog every file on the branch — only what matters to this path (plus minimal shared infra if the path calls it, e.g. rate limiter).
- Keep parallel flows (blasts, API, verifications, other entry points) out of the story body; put them in Notes instead.
Notes section
After the story, add ## Notes for reviewer context that does not belong in the primary path.
Structure
- One
###heading per topic (e.g.### How the bucket works,### Verifications,### Blasts,### API sends,### New column) - Blank line between each section
- Flat
-bullets under each heading with the actual detail. - Bullets are allowed here (unlike the story body).
What to cover
- How shared infrastructure works (e.g. token-bucket math, backend choice).
- Other entry points on the branch that share the same enforcement or schema.
- Migrations, new columns, defaults...etc.
- Anything a reviewer would ask about that you deliberately omitted from the narrative.
What to omit
- Tests — no
*_test.exs, no Hammox/Mox stubs, no fixtures used only in tests, no test plans or verification steps. Same rule as the story body.
Voice
- Same as the story for engineering work: active plural (we added, we enforce, we debit).
- Keep each bullet to one sentence when possible; two only if needed.
- Tight prose — just the facts, not essays. See example.md Notes for target density.
Further reading
- See example.md for the target tone and shape from a completed iteration (story + Notes).
- See examples.md for side-by-side phrasing "do's" and "don't's"
Phase 4: Proofread
Re-read the draft against the code and this checklist:
Accuracy
- Call order matches real invocation chain
- Each
Module.functionexists and is the direct callee - Branch descriptions match actual clauses
- Error atoms, rollback behavior, and UI outcomes match the code
Style
- Active plural + change markers on declarations;
if+ possessives on behavior - No full module paths; no arity on references
- Callbacks to an earlier function use
Module.functionagain if other functions were discussed in between - No bullets in the story body; narrative reads as one path
- Other entry points omitted from the story unless user chose a different path
Notes accuracy
- Parallel paths on the branch are represented in Notes, not missing entirely
- Infra/migration bullets match the code (defaults, flags, rollback, HTTP vs UI outcomes)
- Notes contain no test references (files, stubs, or verification steps)
Fix tmp/pr-story.md in place. Still no user-facing summary after this phase.
Phase 5: Publish to PR (only after explicit approval)
Gate: User must clearly approve the story ("it's ready", "proceed", "go ahead", "publish to PR", etc.). Do not append to the PR before that.
- Re-read
tmp/pr-story.mdfrom disk (do not use an earlier version from context). - Find the PR for the current branch:
gh pr view --json number,body,urlorgh pr list --head "$(git branch --show-current)". - Build the PR body: if the PR already has content, append
tmp/pr-story.mdafter a blank line; if the PR body is empty, use the file alone. Never add anything else that wasn't in the tmp/pr-story.md file to the description. - Update:
gh pr edit <number> --body "$(cat tmp/pr-story.md)"when replacing an empty body, or combine existing body + file via HEREDOC when appending. - Brief confirmation with PR URL only — no re-paste of the full story.