Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

repo-bootstrap.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

repo-bootstrap.tsBlame210 lines · 1 contributor
3ef4c9dClaude1/**
2 * Repo bootstrap — wires up the "full green ecosystem by default" stance.
3 *
4 * Called immediately after a new repository row is created (including on fork).
5 * Every setting defaults to the most protective configuration — all gates on,
6 * auto-repair on, auto-deploy gated on all-green. Owners can turn things off
7 * in settings but they never have to turn things on.
8 *
9 * This is the heart of the "nothing broken reaches the customer" posture.
10 */
11
12import { db } from "../db";
13import {
14 repoSettings,
15 branchProtection,
16 labels,
17 issues,
18 issueComments,
19} from "../db/schema";
20import { audit } from "./notify";
21
22const DEFAULT_LABELS = [
23 { name: "bug", color: "#f85149", description: "Something is broken" },
24 { name: "feature", color: "#1f6feb", description: "New capability" },
25 { name: "enhancement", color: "#58a6ff", description: "Improvement to existing behaviour" },
26 { name: "security", color: "#d29922", description: "Security-related" },
27 { name: "performance", color: "#a371f7", description: "Performance-related" },
28 { name: "docs", color: "#3fb950", description: "Documentation" },
29 { name: "question", color: "#8b949e", description: "Further info requested" },
30 { name: "good first issue", color: "#7ee787", description: "Suitable for new contributors" },
31 { name: "ai-triaged", color: "#bc8cff", description: "Auto-triaged by GlueCron AI" },
32];
33
34const WELCOME_BODY = `Welcome to your new GlueCron repository.
35
36Every repository ships with the **full green ecosystem** enabled by default — nothing broken ever reaches your customers.
37
38## What's enabled out of the box
39
40- **AI code review** on every pull request
41- **Green gate enforcement** — GateTest + AI review + merge check must all pass before merge
42- **Secret & security scanning** on every push
43- **Automated merge conflict resolution** when conflicts arise
44- **AI auto-repair** — failing gates trigger a fix attempt before a human is pinged
45- **Branch protection** on \`main\` — PR required, all gates green, AI approval required
46- **Auto-deploy** to Crontech on every passing push to \`main\`
47- **AI commit messages, PR summaries, and release changelogs** on demand
48
49You can toggle any of this in **Settings → Gates & Auto-repair**. The safe defaults are on.
50
51## Quick start
52
53Push your first commit:
54
55\`\`\`
56git remote add gluecron https://gluecron.com/YOUR_USERNAME/YOUR_REPO.git
57git push -u gluecron main
58\`\`\`
59
60Ask the assistant anything:
61
62\`\`\`
63Click "Ask AI" in the repo nav or press Cmd+K and type your question.
64\`\`\`
65
66Happy shipping.`;
67
68export interface BootstrapResult {
69 settingsCreated: boolean;
70 protectionCreated: boolean;
71 labelsCreated: number;
72 welcomeIssueNumber?: number;
73}
74
75export async function bootstrapRepository(opts: {
76 repositoryId: string;
77 ownerUserId: string;
78 defaultBranch?: string;
79 skipWelcomeIssue?: boolean;
80}): Promise<BootstrapResult> {
81 const branch = opts.defaultBranch || "main";
82 let settingsCreated = false;
83 let protectionCreated = false;
84 let labelsCreated = 0;
85 let welcomeIssueNumber: number | undefined;
86
87 // 1. Settings — all gates on, all AI features on
88 try {
89 await db.insert(repoSettings).values({
90 repositoryId: opts.repositoryId,
91 });
92 settingsCreated = true;
93 } catch (err) {
94 // Ignore unique-violation if settings already exist (fork case)
95 console.warn("[bootstrap] settings:", (err as Error).message);
96 }
97
98 // 2. Branch protection on the default branch — maximum safety
99 try {
100 await db.insert(branchProtection).values({
101 repositoryId: opts.repositoryId,
102 pattern: branch,
103 requirePullRequest: true,
104 requireGreenGates: true,
105 requireAiApproval: true,
106 requireHumanReview: false,
107 requiredApprovals: 0,
108 allowForcePush: false,
109 allowDeletion: false,
110 dismissStaleReviews: true,
111 });
112 protectionCreated = true;
113 } catch (err) {
114 console.warn("[bootstrap] protection:", (err as Error).message);
115 }
116
117 // 3. Default labels
118 try {
119 const rows = DEFAULT_LABELS.map((l) => ({
120 repositoryId: opts.repositoryId,
121 name: l.name,
122 color: l.color,
123 description: l.description,
124 }));
125 await db.insert(labels).values(rows).onConflictDoNothing?.();
126 labelsCreated = rows.length;
127 } catch (err) {
128 // onConflictDoNothing might not be available on all drizzle adapters; best-effort insert
129 for (const l of DEFAULT_LABELS) {
130 try {
131 await db.insert(labels).values({
132 repositoryId: opts.repositoryId,
133 name: l.name,
134 color: l.color,
135 description: l.description,
136 });
137 labelsCreated++;
138 } catch {
139 // already exists — ignore
140 }
141 }
142 }
143
144 // 4. Welcome issue (skippable for forks)
145 if (!opts.skipWelcomeIssue) {
146 try {
147 const [issue] = await db
148 .insert(issues)
149 .values({
150 repositoryId: opts.repositoryId,
151 authorId: opts.ownerUserId,
152 title: "Welcome to GlueCron",
153 body: WELCOME_BODY,
154 state: "open",
155 })
156 .returning();
157 welcomeIssueNumber = issue?.number;
158 } catch (err) {
159 console.warn("[bootstrap] welcome issue:", (err as Error).message);
160 }
161 }
162
163 await audit({
164 userId: opts.ownerUserId,
165 repositoryId: opts.repositoryId,
166 action: "repo.bootstrap",
167 metadata: {
168 settingsCreated,
169 protectionCreated,
170 labelsCreated,
171 welcomeIssueNumber,
172 },
173 });
174
175 return {
176 settingsCreated,
177 protectionCreated,
178 labelsCreated,
179 welcomeIssueNumber,
180 };
181}
182
183/**
184 * Convenience helper to load settings (creates defaults if missing).
185 */
186export async function getOrCreateSettings(repositoryId: string) {
187 const { eq } = await import("drizzle-orm");
188 const [existing] = await db
189 .select()
190 .from(repoSettings)
191 .where(eq(repoSettings.repositoryId, repositoryId))
192 .limit(1);
193 if (existing) return existing;
194
195 try {
196 const [row] = await db
197 .insert(repoSettings)
198 .values({ repositoryId })
199 .returning();
200 return row;
201 } catch {
202 // Race — someone else inserted, re-select
203 const [row] = await db
204 .select()
205 .from(repoSettings)
206 .where(eq(repoSettings.repositoryId, repositoryId))
207 .limit(1);
208 return row;
209 }
210}