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 look | 1 | /** |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 2 | * 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 look | 6 | */ |
| 7 | ||
| 8 | export 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-agent | 23 | /** g1t has been given an issue here. */ |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 24 | agentAssigned: boolean; |
| 25 | }; | |
| 26 | ||
| 27 | export type ChecklistItem = { | |
| Merge branch 'projects-kind-and-links' | 28 | key: "code" | "deploy" | "domain" | "preview" | "checks" | "release" | "production" | "where" | "links" | "instructions" | "agent"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 29 | title: string; |
| 30 | detail: string; | |
| 31 | done: boolean; | |
| 32 | to: string; | |
| 33 | action: string; | |
| 34 | }; | |
| 35 | ||
| 36 | export 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-agent | 83 | title: "Assign a first issue to g1t", |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 84 | 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 production | 92 | export 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 | */ | |
| 106 | export 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 | ||
| Merge branch 'projects-kind-and-links' | 132 | export type StartFacts = Pick<ChecklistFacts, "base" | "hasCode" | "instructions" | "agentAssigned"> & { |
| 133 | /** It has a workflow, whose runs are its pull requests' checks; null when unknown. */ | |
| 134 | hasWorkflow: boolean | null; | |
| 135 | /** Production's address, for an app deployed elsewhere. */ | |
| 136 | productionUrl: string | null; | |
| 137 | /** Its homepage, docs or any other link is set. */ | |
| 138 | hasLinks: boolean; | |
| 139 | /** Its docs address is set, or its homepage. */ | |
| 140 | hasDocsLink: boolean; | |
| 141 | }; | |
| 142 | ||
| 143 | /** Where a project's own settings are, for the steps that are done there. */ | |
| 144 | const settingsAt = (base: string, anchor: string) => `${base}/settings#${anchor}`; | |
| 145 | ||
| 146 | /** | |
| 147 | * The steps for a project g1t does not deploy, by what it is. Every step | |
| 148 | * applies to it and each is done from its own fact: nothing about turning | |
| 149 | * on Deployments, domains or previews. | |
| 150 | * | |
| 151 | * - An app deployed elsewhere: its production address, then checks. | |
| 152 | * - An app nobody has said where it runs: saying so, then checks. | |
| 153 | * - Docs: where they are read. | |
| 154 | * - Anything else: its links. | |
| 155 | */ | |
| 156 | export function startChecklist(kind: "elsewhere" | "unknown" | "docs" | "other", facts: StartFacts): ChecklistItem[] { | |
| 157 | const shared = productionChecklist({ ...facts, deploysEnabled: false, productionDeployed: false, domains: null, previewOpened: false }); | |
| 158 | const step = (key: ChecklistItem["key"]) => shared.find((item) => item.key === key)!; | |
| 159 | const checks: ChecklistItem = { | |
| 160 | key: "checks", | |
| 161 | title: "Add checks on pull requests", | |
| 162 | detail: "A workflow that builds and tests it. Its runs are every pull request's checks.", | |
| 163 | done: facts.hasWorkflow === true, | |
| 164 | to: `${facts.base}/actions`, | |
| 165 | action: "Add CI", | |
| 166 | }; | |
| 167 | const middle: ChecklistItem[] = | |
| 168 | kind === "elsewhere" | |
| 169 | ? [ | |
| 170 | { | |
| 171 | key: "production", | |
| 172 | title: "Add production's address", | |
| 173 | detail: "Where your own pipeline deploys it, so its overview links to production.", | |
| 174 | done: facts.productionUrl != null, | |
| 175 | to: settingsAt(facts.base, "kind"), | |
| 176 | action: "Add", | |
| 177 | }, | |
| 178 | checks, | |
| 179 | ] | |
| 180 | : kind === "unknown" | |
| 181 | ? [ | |
| 182 | { | |
| 183 | key: "where", | |
| 184 | title: "Say where it runs", | |
| 185 | detail: "Deployed on g1t, deployed elsewhere, or not deployed at all, such as a library.", | |
| 186 | done: false, | |
| 187 | to: settingsAt(facts.base, "kind"), | |
| 188 | action: "Choose", | |
| 189 | }, | |
| 190 | checks, | |
| 191 | ] | |
| 192 | : kind === "docs" | |
| 193 | ? [ | |
| 194 | { | |
| 195 | key: "links", | |
| 196 | title: "Add where its docs are read", | |
| 197 | detail: "A docs or homepage address, shown on its overview and wherever the project is listed.", | |
| 198 | done: facts.hasDocsLink, | |
| 199 | to: settingsAt(facts.base, "links"), | |
| 200 | action: "Add", | |
| 201 | }, | |
| 202 | ] | |
| 203 | : [ | |
| 204 | { | |
| 205 | key: "links", | |
| 206 | title: "Add its links", | |
| 207 | detail: "A homepage, docs, or any other address people go to for it.", | |
| 208 | done: facts.hasLinks, | |
| 209 | to: settingsAt(facts.base, "links"), | |
| 210 | action: "Add", | |
| 211 | }, | |
| 212 | ]; | |
| 213 | return [step("code"), ...middle, step("instructions"), step("agent")]; | |
| 214 | } | |
| 215 | ||
| 216 | export type ChecklistPlan = "production" | "release" | "elsewhere" | "unknown" | "docs" | "other"; | |
| 217 | ||
| 218 | /** | |
| 219 | * Which steps a project gets, and their heading, from what it is and where | |
| 220 | * it runs. Only what g1t deploys gets production's steps. | |
| 221 | */ | |
| 222 | export function checklistPlan(project: { | |
| 223 | kind: "app" | "library" | "tool" | "docs" | "other"; | |
| 224 | runs: "g1t" | "elsewhere" | null; | |
| 225 | }): { plan: ChecklistPlan; title: string } { | |
| 226 | if (project.runs === "g1t") return { plan: "production", title: "Get to production" }; | |
| 227 | if (project.kind === "library" || project.kind === "tool") return { plan: "release", title: "Ship a release" }; | |
| 228 | if (project.kind === "app") return { plan: project.runs === "elsewhere" ? "elsewhere" : "unknown", title: "Get started" }; | |
| 229 | return { plan: project.kind, title: "Get started" }; | |
| 230 | } | |
| 231 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 232 | /** `3/6`, for the card's heading. */ |
| 233 | export function progress(items: ChecklistItem[]): { done: number; total: number; complete: boolean } { | |
| 234 | const done = items.filter((item) => item.done).length; | |
| 235 | return { done, total: items.length, complete: done === items.length }; | |
| 236 | } | |
| 237 | ||
| 238 | /** A repository's root lists instructions for agents. */ | |
| 239 | export function hasInstructions(names: string[]): boolean { | |
| 240 | return names.some((name) => /^(agents|claude)\.md$/i.test(name)); | |
| 241 | } | |
| 242 | ||
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 243 | /** 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 look | 244 | export function agentWasAssigned(input: { |
| 245 | runAgents: string[]; | |
| 246 | pullAgents: string[]; | |
| 247 | issues: { assignees: string[]; agent: string | null }[]; | |
| 248 | }): boolean { | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 249 | const isAgent = (name: string | null) => name?.toLowerCase() === "g1t"; |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 250 | return ( |
| 251 | input.runAgents.some(isAgent) || | |
| 252 | input.pullAgents.some(isAgent) || | |
| 253 | input.issues.some((issue) => isAgent(issue.agent) || issue.assignees.some(isAgent)) | |
| 254 | ); | |
| 255 | } | |
| 256 | ||
| 257 | // --- Dismissing it, per project, in this browser ----------------------------- | |
| 258 | ||
| 259 | type Store = Pick<Storage, "getItem" | "setItem">; | |
| 260 | ||
| 261 | export function dismissKey(base: string): string { | |
| 262 | return `g1t:checklist-dismissed:${base.toLowerCase()}`; | |
| 263 | } | |
| 264 | ||
| 265 | export function isDismissed(storage: () => Store | null | undefined, base: string): boolean { | |
| 266 | try { | |
| 267 | return storage()?.getItem(dismissKey(base)) === "1"; | |
| 268 | } catch { | |
| 269 | return false; | |
| 270 | } | |
| 271 | } | |
| 272 | ||
| 273 | export function dismiss(storage: () => Store | null | undefined, base: string): boolean { | |
| 274 | try { | |
| 275 | const store = storage(); | |
| 276 | if (!store) return false; | |
| 277 | store.setItem(dismissKey(base), "1"); | |
| 278 | return true; | |
| 279 | } catch { | |
| 280 | return false; | |
| 281 | } | |
| 282 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.