Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
52 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.

The runner base keeps its old comment, so the pushed base image still matches its folder1/**
2 * A postmortem's starting point: the timeline written out from the
3 * incident's own, the impact from its parts and times, and the follow-ups
4 * as action items. Staff edit it before publishing. No Workers imports.
5 */
6import type { AdminIncident, FollowUp, PostmortemFields, TimelineEntry } from "@g1t/contracts";
7
8import { duration } from "./incidents.ts";
9import { IMPACT_WORD } from "./model.ts";
10
11const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
12
13/** "5 Oct 14:03 UTC". */
14export function stamp(at: string): string {
15 const d = new Date(at);
16 const hh = String(d.getUTCHours()).padStart(2, "0");
17 const mm = String(d.getUTCMinutes()).padStart(2, "0");
18 return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${hh}:${mm} UTC`;
19}
20
21/** Lines not worth a reader's time: acknowledgements, follow-up ticks, the postmortem itself. */
22const SKIP = new Set(["acknowledged", "followup", "postmortem"]);
23
24/** The internal timeline as a list, one line per entry, public updates marked. */
25export function timelineText(entries: TimelineEntry[]): string {
26 return entries
27 .filter((e) => !SKIP.has(e.kind))
28 .map((e) => `- ${stamp(e.at)}: ${e.kind === "update" ? "Status page: " : ""}${e.text.replace(/\s*\n+\s*/g, " ")}`)
29 .join("\n");
30}
31
32export function postmortemDraft(
33 incident: Pick<AdminIncident, "started_at" | "resolved_at" | "components" | "durations">,
34 timeline: TimelineEntry[],
35 followups: FollowUp[],
36 names: Map<string, string>,
37): PostmortemFields {
38 const parts = incident.components
39 .filter((c) => c.impact !== "operational")
40 .map((c) => `${names.get(c.key) ?? c.key} (${IMPACT_WORD[c.impact].toLowerCase()})`);
41 const lasted = incident.durations.to_resolve != null ? ` for ${duration(incident.durations.to_resolve)}` : "";
42 const window = `from ${stamp(incident.started_at)}${incident.resolved_at ? ` to ${stamp(incident.resolved_at)}` : ""}`;
43 return {
44 summary: "",
45 impact: `${parts.join(", ") || "No part"} affected${lasted}, ${window}.`,
46 timeline: timelineText(timeline),
47 root_cause: "",
48 went_well: "",
49 went_badly: "",
50 action_items: followups.map((f) => `- ${f.title}`).join("\n"),
51 };
52}

This file's history is long; its oldest lines are credited to the oldest commit read.