| name | contribute |
| description | Find a real upstream PrairieLearn bug with no open PR, reproduce + fix it in the harness, and publish it as an issue + pull request ON THE USER'S OWN FORK (never upstream) — packaged as a human-readable proposal a maintainer could copy over. Wraps debug-issue with a discovery front-end, a dual-test proof (PL's own tests + harness evidence), and a fork-only SHARE phase. Runs inline. Use when the user says "contribute a fix", "open a PR on my fork", "propose a fix to PrairieLearn", "find an issue to fix and PR it", or "/contribute". |
contribute
Turn a real PrairieLearn bug into a polished, evidence-backed pull request on the user's own public fork — without ever touching upstream PrairieLearn (no issues, no PRs, no comments, no cross-references). The PR is a proposal: packaged so a maintainer could review it and copy it over on their own machine, on their own time.
Invoke: /contribute (discover a candidate) · /contribute 14640 (you already picked an upstream issue to mirror).
Read first: reference/contributing-upstream.md — the discovery funnel, PrairieLearn's contribution conventions and language, the dual-test model, and the never-touch-upstream rules. That doc is the contract; this skill is the sequence.
Hard boundaries (non-negotiable)
- Fork-only. Every GitHub write goes to
<your-gh-user>/PrairieLearn. scripts/share-journey.sh targets the fork by construction; the guard-upstream-push.sh hook hard-blocks any push/PR/issue to PrairieLearn/PrairieLearn.
- No active upstream interaction. Never open PRs/issues or post comments/reviews on
PrairieLearn/PrairieLearn. A clickable reference link to the upstream issue is fine (fork→parent cross-references are generally suppressed).
- Explicit go-ahead before anything outward. Discover, reproduce, fix, and test first; produce the issue + PR bodies; then STOP and ask before
share-journey.sh runs.
- Base on current upstream master. The fix branch is cut from current
origin/master (spawn-journey fetches first) and the fork's master is synced to upstream at publish — so the PR is clean and applies to what maintainers actually have. A stale local checkout causes conflicts.
Flow
0. DISCOVER — pick a candidate (skip if given a number)
Run the funnel in the reference doc: pull open issues by keyword (PL has no bug label); prefer no open PR, but an open-PR bug is an allowed blind double-up (Part H — solve blind, then judge ours vs theirs); skip only merged-fix issues. Score harness-fit (reproducibility · localization · fix-size · observability · still-valid), shortlist 2–3 (any difficulty — harder is welcome), recommend one. Present the shortlist; let the user choose.
1. SPIKE — de-risk before committing
A quick PRE-FLIGHT + the fastest reproduction available (a slice, or a short journey) to confirm the bug actually reproduces in our stack. If it doesn't reproduce on current master it may be already fixed → switch to the already-resolved path (reference Part I): prove it empirically and file a fork "appears resolved" close-suggestion issue (no PR). Only truly drop a candidate if you can neither reproduce it nor prove resolution. Never file a fix for a bug you haven't reproduced empirically.
2. FIX — the debug-issue journey
Run debug-issue end-to-end (PRE-FLIGHT → GATHER → SCAN → CLASSIFY → BUILD) on the chosen bug. Output: the fix in the worktree + fix-summary.md with before/after evidence (trace timeline and/or before/after screenshots).
3. PROVE — dual testing (both halves required)
- Their testing (maintainer-trusted). Add a regression test in PL's idiom at the right layer (reference doc: pure-fn
*.test.ts; route/render src/tests/*.test.ts with helperServer + fetchCheerio — there are no .test.tsx; Python element *_test.py). Run it red on the base branch, green with the fix, plus the neighboring tests for the changed module — all via ./scripts/run-pl-tests.sh --issue N <path> (runs in the warm container, where PL's support services are already up). Triage any neighbor failure (Part D): environmental/pre-existing, or a test encoding the old behavior (update it preserving intent), or a true regression (rethink the fix) — never weaken a neighbor to make it pass.
- Full-suite check — no new failures vs base. Run PL's whole suite, capping workers so it fits memory:
./scripts/run-pl-tests.sh --issue N --cmd 'vitest run --maxWorkers=2' and --cmd 'uv run pytest' (add e2e when a route/render/client flow is affected). Bar: no failures absent on the base branch; triage each failure (Part D: environmental/pre-existing vs encodes-old-behavior vs true regression). Record it in the PR's Testing section. Many fixes in parallel under a memory cap → one serialized capped sweep across all branches before review, not a concurrent full suite per PR (concurrent full suites OOM).
- Our testing (harness evidence). The empirical before/after from the journey — trace timeline for logic bugs, before/after screenshots for visual bugs. Transcribe it into the issue/PR — the concrete observed before/after, not a claim one exists; the reviewer never ran your harness, so evidence you don't write down is thrown away.
- Red-team the diff against sibling inputs (the red-team skill) — prove no unintended side effects, and state the outcome in the PR (which siblings you exercised, that each held).
- Query performance audit — REQUIRED in the PR whenever SQL/queries change. Read each changed query (Part D): a projection-only change → note "no plan impact"; a WHERE/JOIN/ORDER/GROUP/new-table change →
EXPLAIN (ANALYZE, BUFFERS) on representative data and compare plan+cost to base. Check for new full scans, lost index use, N+1, cross joins, expensive per-row work. Beyond the plan, performance-test it: measure before→after execution time on representative-scale data and document the speed impact (correct-but-slower is a regression). Document the audit + the timing test in the PR's Testing section — no SQL/schema change ships without it.
- GATE: regression test goes red→green, neighbors stay green, the full suite shows no failures absent on the base branch, red-team clean, and — if any SQL/query/schema changed — a query performance audit + performance test (measured before→after timing) is documented in the PR. No publish otherwise.
4. PACKAGE — the issue + PR bodies
Fill templates/issue-body.md → journeys/<id>/issue-body.md and templates/pr-body.md → journeys/<id>/pr-body.md from fix-summary.md, in PL's language (sentence case, PL vocabulary, terse bullets).
- Reference the original upstream issue in both bodies — a visible, clickable link (e.g.
[PrairieLearn/PrairieLearn#<N>](https://github.com/PrairieLearn/PrairieLearn/issues/<N>)).
- If the fix changes anything a user sees rendered — layout or text/label/message/hint — before/after screenshots are REQUIRED — lead the PR with them, each captioned. Host them with
./scripts/upload-evidence.sh --issue N <imgs> (pushes to an evidence/<id> branch and prints inline-rendering raw URLs; never part of the fix diff). The exception is per-surface, not per-PR: skip screenshots only for a diff with no user-facing rendered output at all (pure backend/logic/SQL/grading-math/data). A changed user-facing string IS a rendered change — a mixed logic+text fix still owes screenshots for the text. Decide by what the fix changes, not by whether the bug first looked visual.
- Justify the fix (template scaffolds these — keep prose minimal): a before → after code block, a one-line why it's correct tied to the root cause, and a regression check that bounds the blast radius — every caller/consumer of the changed symbol you traced, the neighbors left unchanged (with their passing tests), and the red-team siblings.
- For a double-up (the bug has an open upstream PR you solved blind): include a "Comparison with open PR #N" section — link it, summarize each approach, and state the verdict (correctness / scope / tests / side-effects) and why (Part H). Don't discard the comparison.
- Add a Mermaid diagram only when a flow/sequence is clearer as a picture than prose.
- Disclose AI assistance honestly in the PR's mandatory checkbox.
5. SHARE — fork-only, on explicit go-ahead
Show the issue + PR bodies for review. After the user says go:
./scripts/inject-trace.sh --worktree <wt> --revert — strip every trace line.
- Commit the fix + regression test on the journey branch. Message: title
<Verb> <effect> (sentence case) + a short body + a Co-Authored-By: Claude ... trailer.
./scripts/share-journey.sh --issue N --title "<title>" --issue-body journeys/<id>/issue-body.md --pr-body journeys/<id>/pr-body.md (preview with --dry-run first) — forks if needed → enables Issues → pushes to fork → files the fork issue → opens the fork-internal PR.
- Confirm the fork PR is MERGEABLE/CLEAN (
gh pr view <n> -R <fork> --json mergeable,mergeStateStatus) — a CONFLICTING or behind PR means the branch wasn't on current origin/master; rebase there, force-push, and gh repo sync the fork.
6. TEAR DOWN — always clean up after solving
./scripts/cleanup.sh --issue N --keep-artifacts — stop the warm stack and remove the worktree (the fork issue, PR, and branch stay on GitHub; artifacts are retained). Always run this when the journey ends, whether or not it was published — don't leave stacks/worktrees running.
7. REFLECT — generalized self-improvement (always, last)
Close with the reflex (→ rules/generalize.md): what generalized instruction change would help a future blind agent? Apply only what generalizes, adds no contradiction, and leaves the instruction set leaner — else change nothing.
Don't
- Don't open a PR against upstream, or comment on an upstream issue, ever.
- Don't @-mention or name any maintainer or developer in issue/PR bodies (it notifies real people). Refer to prior discussion generically ("the issue", "a maintainer", "a contributor") — never copy a
@handle or a person's name out of the upstream issue/PR. Cite the upstream issue only as a plain clickable URL link, never a bare #N / owner/repo#N (which cross-references and pings).
- Don't file the fork issue before the SPIKE reproduces the bug.
- Don't publish without the regression test going red→green and the user's explicit go-ahead.
- Don't let trace injection ride along in the PR diff — revert first.