skip to content
Multi-question ask + composite scoring05 / 05

Pull Request Horoscope

Mercury is in retrograde and so is your migration.

flow graph · graphOf(pr-horoscope)
ask · read-the-praskRead the PRstep · horoscopestepCast the horoscope
  • calls jev
  • your code
  • leaf / emit
  • fork / join
packages/examples/src/pr-horoscope.ts
1/**
2 * Pull Request Horoscope
3 * Pattern: one call, many questions, combined in code.
4 *
5 * A single `ask` sends five questions about the same PR in one request. A step
6 * turns the typed answers into a risk number and a horoscope. Note how
7 * `a.scope.choice` is typed as the literal union of labels: no string typos.
8 */
9import { ask, chain, choice, noul, score, step, type OutputOf } from "jevchain";
10import type { Example } from "./types";
12const read = ask("read-the-pr", {
13 title: "Read the PR",
14 questions: {
15 clarity: score("How clearly does the description explain what changed and why?", ["no description", "vague", "clear", "exemplary"]),
16 tests: noul("Does the PR add or update tests?"),
17 yolo: noul("Does the description suggest it was rushed? ('quick fix', 'should be fine', 'lgtm', no testing notes)"),
18 friday: noul("Does the PR mention deploying on a Friday or right before a holiday?"),
19 scope: choice("What kind of change is this?", {
20 typo: "Docs, copy or a one-line fix",
21 feature: "New user-facing functionality",
22 refactor: "Restructuring code without changing behaviour",
23 migration: "Database, infrastructure or dependency changes",
24 }),
25 },
26});
28const SCOPE_RISK = { typo: 0.05, feature: 0.4, refactor: 0.5, migration: 0.8 } as const;
30const horoscope = step("horoscope", (a: OutputOf<typeof read>) => {
31 const risk =
32 0.35 * SCOPE_RISK[a.scope.choice] +
33 0.2 * (1 - a.clarity.score / 3) +
34 0.15 * (1 - a.tests.noul) +
35 0.15 * a.yolo.noul +
36 0.15 * a.friday.noul;
37 const sign = risk < 0.25 ? "♉ Taurus: steady, dependable, merge it." : risk < 0.5 ? "♊ Gemini: two reviewers, minimum." : risk < 0.7 ? "♏ Scorpio: something is hiding in this diff." : "♈ Aries: chaos. Roll back before you roll out.";
38 return { risk: Math.round(risk * 100) / 100, scope: a.scope.choice, sign };
39}, { title: "Cast the horoscope" });
41export const prHoroscope = chain("pr-horoscope", read, horoscope);
43export const example: Example = {
44 slug: "pr-horoscope",
45 title: "Pull Request Horoscope",
46 tagline: "Mercury is in retrograde and so is your migration.",
47 pattern: "Multi-question ask + composite scoring",
48 lesson:
49 "Ask everything you need about one input in a single call (it's cheaper and faster than one call per question), then combine typed answers in plain code.",
50 chain: prHoroscope,
51 file: "pr-horoscope.ts",
52 inputs: [
53 {
54 label: "Friday migration",
55 value: {
56 title: "quick fix for users table",
57 description: "drops the legacy_email column, should be fine. deploying friday evening so it's quiet",
58 filesChanged: 1,
59 },
60 },
61 {
62 label: "Typo fix",
63 value: { title: "Fix typo in README", description: "'recieve' → 'receive'.", filesChanged: 1 },
64 },
65 {
66 label: "Well-behaved feature",
67 value: {
68 title: "Add CSV export to reports",
69 description:
70 "Adds an Export button to the reports page. Streams rows so large reports don't time out. Added unit tests for the serializer and an e2e test for the download. Behind the `csv_export` flag.",
71 filesChanged: 7,
72 },
73 },
74 ],
75};

api key

bring your own typesafe key, or ride the shared one (rate-limited, be nice).

shared key
checking…
your key
not set

your key stays in this browser (localStorage, jevchain.byok). it only travels to this site's /api/jev proxy in an x-typesafe-key header, which forwards it to typesafe and immediately forgets it. nothing is logged or stored server-side. requests on your own key get a much roomier rate limit.

keyboard shortcuts

fewer clicks, more chains. these work anywhere outside a text field.