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 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | /**
* 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,
issueLabels,
repositories,
} 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" },
{ name: "ai:build", color: "#f0883e", description: "Autopilot will implement this and open a PR" },
];
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 Vapron 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,
};
}
const AI_BUILD_SEED_TITLE = "Add a welcome README with project overview";
const AI_BUILD_SEED_BODY = `## What to build
Add a \`README.md\` to this repository with:
- A one-paragraph description of what this project does
- Quick start instructions (install + run)
- A brief note about the tech stack
## Why this issue exists
This issue is labelled \`ai:build\`. Gluecron's autopilot will automatically:
1. Read this spec
2. Ask Claude to implement it
3. Open a draft pull request with the code
You don't need to do anything. Just watch the PR appear. ✨
To build your own features this way, open an issue, describe what you want, and add the \`ai:build\` label.`;
/**
* Seeds an `ai:build`-labelled issue on a user's FIRST repository so they
* immediately discover the autopilot feature.
*
* Silently skips if this is not the owner's first repo, or if anything fails.
* Must be called fire-and-forget — it never throws.
*/
export async function ensureAiBuildSeedIssue(
repositoryId: string,
ownerId: string
): Promise<void> {
try {
const { eq, and, sql } = await import("drizzle-orm");
// 1. Only seed on the owner's first repo.
const [{ n }] = await db
.select({ n: sql<number>`count(*)::int` })
.from(repositories)
.where(eq(repositories.ownerId, ownerId));
if ((n ?? 0) > 1) return;
// 2. Find the ai:build label for this repo (seeded by bootstrapRepository).
const [label] = await db
.select({ id: labels.id })
.from(labels)
.where(and(eq(labels.repositoryId, repositoryId), eq(labels.name, "ai:build")))
.limit(1);
if (!label) {
console.warn("[ai-build-seed] ai:build label not found for repo", repositoryId);
return;
}
// 3. Create the seed issue.
const [issue] = await db
.insert(issues)
.values({
repositoryId,
authorId: ownerId,
title: AI_BUILD_SEED_TITLE,
body: AI_BUILD_SEED_BODY,
state: "open",
})
.returning();
if (!issue) return;
// 4. Attach the ai:build label.
await db.insert(issueLabels).values({ issueId: issue.id, labelId: label.id });
} catch (err) {
console.warn("[ai-build-seed] failed:", (err as Error)?.message);
}
}
/**
* 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;
}
}
|