g1t/apps/web/app/lib/checklist.ts

182 lines6,267 bytesCodeBlame

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.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1/**
A project is an app or a library: libraries show their package and how to ship a release, not production2 * The steps that take a project to production, or for a library to its
3 * first release, each worked out from what the project has done, with
4 * where to do the ones it has not. Shown on the project's overview to its
5 * members until they are all done or it is dismissed.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look6 */
7
8export type ChecklistFacts = {
9 /** The project's address, `/<workspace>/<project>`. */
10 base: string;
11 /** Its default branch has a commit, or its source is a mirror. */
12 hasCode: boolean;
13 /** Deployments are switched on for it. */
14 deploysEnabled: boolean;
15 /** A production build has gone live, now or before. */
16 productionDeployed: boolean;
17 /** Custom domains it has; null when they could not be read. */
18 domains: number | null;
19 /** A preview has been built for a branch or pull request. */
20 previewOpened: boolean;
21 /** An AGENTS.md or CLAUDE.md at the root of its default branch; null when unknown. */
22 instructions: boolean | null;
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent23 /** g1t has been given an issue here. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look24 agentAssigned: boolean;
25};
26
27export type ChecklistItem = {
A project is an app or a library: libraries show their package and how to ship a release, not production28 key: "code" | "deploy" | "domain" | "preview" | "checks" | "release" | "instructions" | "agent";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look29 title: string;
30 detail: string;
31 done: boolean;
32 to: string;
33 action: string;
34};
35
36export function productionChecklist(facts: ChecklistFacts): ChecklistItem[] {
37 const { base } = facts;
38 return [
39 {
40 key: "code",
41 title: "Connect a source or push code",
42 detail: "Push an existing project, or have your coding agent start one.",
43 done: facts.hasCode,
44 to: `${base}/code`,
45 action: "Push code",
46 },
47 {
48 key: "deploy",
49 title: "Deploy to production",
50 detail: facts.deploysEnabled
51 ? "Production builds from the default branch on every push."
52 : "Deployments are off until you turn them on. Then production builds from the default branch on every push.",
53 done: facts.productionDeployed,
54 to: facts.deploysEnabled ? `${base}/deployments` : `${base}/settings/deployments`,
55 action: facts.deploysEnabled ? "Deployments" : "Turn on",
56 },
57 {
58 key: "domain",
59 title: "Add a custom domain",
60 detail: "Serve production from a domain of your own.",
61 done: (facts.domains ?? 0) > 0,
62 to: `${base}/settings/domains`,
63 action: "Add",
64 },
65 {
66 key: "preview",
67 title: "Open a preview",
68 detail: "Every pull request gets a live preview of its branch.",
69 done: facts.previewOpened,
70 to: `${base}/pulls/new`,
71 action: "New pull request",
72 },
73 {
74 key: "instructions",
75 title: "Set up repository instructions",
76 detail: "Commit an AGENTS.md with how to build and test; every agent run reads it.",
77 done: facts.instructions === true,
78 to: `${base}/agents#instructions`,
79 action: "How",
80 },
81 {
82 key: "agent",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent83 title: "Assign a first issue to g1t",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look84 detail: "It opens a pull request, makes the change and sees it through checks and review.",
85 done: facts.agentAssigned,
86 to: `${base}/issues/new`,
87 action: "New issue",
88 },
89 ];
90}
91
A project is an app or a library: libraries show their package and how to ship a release, not production92export type ReleaseFacts = Pick<ChecklistFacts, "base" | "hasCode" | "instructions" | "agentAssigned"> & {
93 /** It has a workflow, whose runs are its pull requests' checks; null when unknown. */
94 hasWorkflow: boolean | null;
95 /** A package its repository publishes has a version. */
96 released: boolean;
97 /** Where publishing a first version is explained: its package's page or its registry's guide. */
98 releaseTo: string;
99};
100
101/**
102 * The steps for a library or a tool, which ships as releases rather than
103 * deploying: the same first and last steps as production, with checks and
104 * a first version in between.
105 */
106export function releaseChecklist(facts: ReleaseFacts): ChecklistItem[] {
107 const shared = productionChecklist({ ...facts, deploysEnabled: false, productionDeployed: false, domains: null, previewOpened: false });
108 const step = (key: ChecklistItem["key"]) => shared.find((item) => item.key === key)!;
109 return [
110 step("code"),
111 {
112 key: "checks",
113 title: "Add checks on pull requests",
114 detail: "A workflow that builds and tests it. Its runs are every pull request's checks.",
115 done: facts.hasWorkflow === true,
116 to: `${facts.base}/actions`,
117 action: "Add CI",
118 },
119 {
120 key: "release",
121 title: "Tag a release or publish a package",
122 detail: "Publish a first version to the workspace's registry for others to install.",
123 done: facts.released,
124 to: facts.releaseTo,
125 action: "How",
126 },
127 step("instructions"),
128 step("agent"),
129 ];
130}
131
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look132/** `3/6`, for the card's heading. */
133export function progress(items: ChecklistItem[]): { done: number; total: number; complete: boolean } {
134 const done = items.filter((item) => item.done).length;
135 return { done, total: items.length, complete: done === items.length };
136}
137
138/** A repository's root lists instructions for agents. */
139export function hasInstructions(names: string[]): boolean {
140 return names.some((name) => /^(agents|claude)\.md$/i.test(name));
141}
142
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent143/** Whether g1t has worked here, from its runs, pull requests or issues. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look144export function agentWasAssigned(input: {
145 runAgents: string[];
146 pullAgents: string[];
147 issues: { assignees: string[]; agent: string | null }[];
148}): boolean {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent149 const isAgent = (name: string | null) => name?.toLowerCase() === "g1t";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look150 return (
151 input.runAgents.some(isAgent) ||
152 input.pullAgents.some(isAgent) ||
153 input.issues.some((issue) => isAgent(issue.agent) || issue.assignees.some(isAgent))
154 );
155}
156
157// --- Dismissing it, per project, in this browser -----------------------------
158
159type Store = Pick<Storage, "getItem" | "setItem">;
160
161export function dismissKey(base: string): string {
162 return `g1t:checklist-dismissed:${base.toLowerCase()}`;
163}
164
165export function isDismissed(storage: () => Store | null | undefined, base: string): boolean {
166 try {
167 return storage()?.getItem(dismissKey(base)) === "1";
168 } catch {
169 return false;
170 }
171}
172
173export function dismiss(storage: () => Store | null | undefined, base: string): boolean {
174 try {
175 const store = storage();
176 if (!store) return false;
177 store.setItem(dismissKey(base), "1");
178 return true;
179 } catch {
180 return false;
181 }
182}