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 | /** |
| 2 | * The steps that take a project to production, each worked out from what | |
| 3 | * the project has done, with where to do the ones it has not. Shown on the | |
| 4 | * project's overview to its members until they are all done or it is | |
| 5 | * dismissed. | |
| 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; | |
| 23 | /** g1t-agent has been given an issue here. */ | |
| 24 | agentAssigned: boolean; | |
| 25 | }; | |
| 26 | ||
| 27 | export type ChecklistItem = { | |
| 28 | key: "code" | "deploy" | "domain" | "preview" | "instructions" | "agent"; | |
| 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", | |
| 83 | title: "Assign a first issue to g1t-agent", | |
| 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 | ||
| 92 | /** `3/6`, for the card's heading. */ | |
| 93 | export function progress(items: ChecklistItem[]): { done: number; total: number; complete: boolean } { | |
| 94 | const done = items.filter((item) => item.done).length; | |
| 95 | return { done, total: items.length, complete: done === items.length }; | |
| 96 | } | |
| 97 | ||
| 98 | /** A repository's root lists instructions for agents. */ | |
| 99 | export function hasInstructions(names: string[]): boolean { | |
| 100 | return names.some((name) => /^(agents|claude)\.md$/i.test(name)); | |
| 101 | } | |
| 102 | ||
| 103 | /** Whether g1t-agent has worked here, from its runs, pull requests or issues. */ | |
| 104 | export function agentWasAssigned(input: { | |
| 105 | runAgents: string[]; | |
| 106 | pullAgents: string[]; | |
| 107 | issues: { assignees: string[]; agent: string | null }[]; | |
| 108 | }): boolean { | |
| 109 | const isAgent = (name: string | null) => name?.toLowerCase() === "g1t-agent"; | |
| 110 | return ( | |
| 111 | input.runAgents.some(isAgent) || | |
| 112 | input.pullAgents.some(isAgent) || | |
| 113 | input.issues.some((issue) => isAgent(issue.agent) || issue.assignees.some(isAgent)) | |
| 114 | ); | |
| 115 | } | |
| 116 | ||
| 117 | // --- Dismissing it, per project, in this browser ----------------------------- | |
| 118 | ||
| 119 | type Store = Pick<Storage, "getItem" | "setItem">; | |
| 120 | ||
| 121 | export function dismissKey(base: string): string { | |
| 122 | return `g1t:checklist-dismissed:${base.toLowerCase()}`; | |
| 123 | } | |
| 124 | ||
| 125 | export function isDismissed(storage: () => Store | null | undefined, base: string): boolean { | |
| 126 | try { | |
| 127 | return storage()?.getItem(dismissKey(base)) === "1"; | |
| 128 | } catch { | |
| 129 | return false; | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | export function dismiss(storage: () => Store | null | undefined, base: string): boolean { | |
| 134 | try { | |
| 135 | const store = storage(); | |
| 136 | if (!store) return false; | |
| 137 | store.setItem(dismissKey(base), "1"); | |
| 138 | return true; | |
| 139 | } catch { | |
| 140 | return false; | |
| 141 | } | |
| 142 | } |