Skip to content
135 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened1/**
2 * What a routine can run on, in words, and routines that fit an agent's
3 * responsibilities, for owners to add in one step: Margo's "Reviewing pull
4 * requests for risk and test coverage" runs when a pull request is ready
5 * for review. Matched on words, never by a model, so it is instant and the
6 * same every time. Pure, so it is tested on its own.
7 */
8import type { NewRoutine, RoutineEvent, RoutineSuggestion } from "@g1t/contracts";
9
10/** The events a routine can run on, by key. */
11export const EVENT_KEYS: RoutineEvent[] = ["pull_ready", "pull_merged", "checks_failed", "issue_opened", "deploy_failed"];
12
13const WORDS: Record<RoutineEvent, string> = {
14 pull_ready: "a pull request is ready for review",
15 pull_merged: "a pull request is merged",
16 checks_failed: "checks fail on a pull request",
17 issue_opened: "an issue is opened",
18 deploy_failed: "a deploy fails",
19};
20
21/** A routine's events in words: "When a pull request is ready for review or checks fail on a pull request". */
22export function describeEvents(events: RoutineEvent[]): string {
23 const list = events.map((e) => WORDS[e]).filter(Boolean);
24 if (!list.length) return "";
25 const joined = list.length === 1 ? list[0] : `${list.slice(0, -1).join(", ")} or ${list.at(-1)}`;
26 return `When ${joined}`;
27}
28
29const trimmed = (duty: string) => duty.trim().replace(/\.$/, "");
30
31type Rule = { match: RegExp; routine: (duty: string) => Omit<NewRoutine, "channel_id"> };
32
33const RULES: Rule[] = [
34 {
A technical writer's 'update the docs after every change' suggests a routine that keeps the docs current when a pull request merges35 match: /\b(docs|documentation|pages|runbooks?)\b.*\b(change|changes|merge|merges|wrong|current|up to date|stale)\b/i,
36 routine: (duty) => ({
37 name: "Keep the docs current",
38 instructions: `When a pull request is merged, keep the docs true (${trimmed(duty)}). Find the pages it makes wrong or incomplete: pages marked possibly out of date by this change first, then search_docs for what it changed. Update each with edit_page (it becomes a suggestion where you can't edit), citing the pull request. Post a short list of what you changed here; say so if nothing needed changing.`,
39 schedule: null,
40 events: ["pull_merged"],
41 repos: [],
42 }),
43 },
44 {
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened45 match: /\b(review|reviewing|reviews)\b.*\b(pull requests?|prs?|changes?|code)\b|\b(pull requests?|prs?)\b.*\breview/i,
46 routine: (duty) => ({
47 name: "Review pull requests",
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar48 instructions: `When a pull request is ready for review, review it (${trimmed(duty)}). Read the change and its checks, then post your review on the pull request with review_pull: what it changes, the risks, which tests cover it and what they miss, and a verdict (approve, or request changes with what to fix first). Then post a two-line summary here with a link.`,
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened49 schedule: null,
50 events: ["pull_ready"],
51 repos: [],
52 }),
53 },
54 {
55 match: /\b(flaky|failing|broken)\b.*\b(checks?|tests?|builds?|ci)\b|\b(checks?|ci|builds?)\b.*\b(flaky|fail)/i,
56 routine: (duty) => ({
57 name: "Chase failing checks",
58 instructions: `When checks fail on a pull request, look into it (${trimmed(duty)}). Read the failure, say whether it looks real or flaky and why, and what to do next. If a flaky check keeps coming back, offer to file an issue.`,
59 schedule: null,
60 events: ["checks_failed"],
61 repos: [],
62 }),
63 },
64 {
65 match: /\btest plans?\b|\bnew features?\b.*\btest/i,
66 routine: (duty) => ({
67 name: "Test plans for new work",
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar68 instructions: `When an issue is opened for new work, draft a test plan (${trimmed(duty)}): what to test, the edge cases, and what can be automated. Post it on the issue with comment, and a one-line note here. Skip bug reports and questions.`,
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened69 schedule: null,
70 events: ["issue_opened"],
71 repos: [],
72 }),
73 },
74 {
75 match: /\b(triage|triaging)\b|\bincoming (issues|bugs|requests)\b/i,
76 routine: (duty) => ({
77 name: "Triage new issues",
78 instructions: `When an issue is opened, triage it (${trimmed(duty)}): what it is (bug, feature, question), how urgent, which area of the code it touches, and who should own it.`,
79 schedule: null,
80 events: ["issue_opened"],
81 repos: [],
82 }),
83 },
84 {
85 match: /\b(deploys?|deployments?|incidents?|on-?call|outages?|rollbacks?)\b/i,
86 routine: (duty) => ({
87 name: "Watch deploys",
88 instructions: `When a deploy fails, find out why (${trimmed(duty)}): what failed, what changed since the last good deploy, and whether to retry, roll back or fix forward.`,
89 schedule: null,
90 events: ["deploy_failed"],
91 repos: [],
92 }),
93 },
94 {
95 match: /\b(release notes|changelog|what shipped|weekly (summary|update|digest)|digests?)\b/i,
96 routine: (duty) => ({
97 name: "Weekly summary",
98 instructions: `Every Friday (${trimmed(duty)}): what merged and shipped this week, what's still open and what's at risk, in a short post people can skim.`,
99 schedule: { every: "week", minute: 0, hour: 16, weekday: 5 },
100 events: [],
101 repos: [],
102 }),
103 },
104 {
105 match: /\b(support|customers?|tickets?|complaints?|feedback)\b/i,
106 routine: (duty) => ({
107 name: "Support themes",
108 instructions: `Every weekday morning (${trimmed(duty)}): read yesterday's conversations you can see, group questions and complaints into themes, and post the top ones with how often each came up and whether an issue already covers it.`,
109 schedule: { every: "weekday", minute: 0, hour: 9, weekday: 1 },
110 events: [],
111 repos: [],
112 }),
113 },
114];
115
116/**
117 * Routines for an agent's responsibilities that it doesn't have yet: one
118 * per kind, skipping those whose name it already uses or whose events its
119 * routines already run on.
120 */
121export function suggestRoutines(responsibilities: string[], existing: { name: string; events: RoutineEvent[] }[]): RoutineSuggestion[] {
122 const out: RoutineSuggestion[] = [];
123 const taken = new Set(existing.map((r) => r.name.toLowerCase()));
124 const covered = new Set(existing.flatMap((r) => r.events));
125 for (const duty of responsibilities) {
126 const rule = RULES.find((r) => r.match.test(duty));
127 if (!rule) continue;
128 const routine = rule.routine(duty);
129 if (taken.has(routine.name.toLowerCase())) continue;
130 if (routine.events?.length && routine.events.every((e) => covered.has(e))) continue;
131 taken.add(routine.name.toLowerCase());
132 out.push({ responsibility: duty, routine });
133 }
134 return out;
135}