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