Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepfix/audit-sweep-2026-07-26gatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
migration-onboarding.ts5.2 KB · 141 lines
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
/**
 * Post-import onboarding for a team migrating from GitHub.
 *
 * `POST /import/bulk` cloned repositories and inserted rows, and stopped
 * there. A team that migrated an org therefore landed on a set of bare
 * repositories with no branch protection, no labels, no CODEOWNERS, no gate
 * settings, and no evidence that any of it was worth doing. The platform's
 * entire value proposition — that it finds problems in your code — was
 * something they had to go and discover for themselves, one repo at a time.
 *
 * This runs the work for them at import time and returns a report:
 *
 *   1. bootstrapRepository()      green defaults: gates on, branch
 *                                 protection, labels, CODEOWNERS.
 *   2. runSecretAndSecurityScan() the actual proof — committed secrets and
 *                                 security findings in the code they just
 *                                 brought over.
 *
 * Everything here is best-effort by design. A migration that imported 40
 * repositories must not be reported as a failure because the scanner choked
 * on one of them, so each repo's outcome is captured independently and a
 * thrown error becomes a per-repo `error` field rather than a rejected
 * promise.
 *
 * Concurrency is bounded (DB_FANOUT_LIMIT). Scanning is IO-heavy — a tree
 * walk plus blob reads per repo — and an unbounded Promise.all over a
 * 100-repo org would open 100 concurrent walks against the same disk and
 * connection pool.
 */

import { mapWithConcurrency, DB_FANOUT_LIMIT } from "./concurrency";

/** One repository's post-import outcome. */
export interface MigrationRepoReport {
  owner: string;
  name: string;
  /** Green-default bootstrap applied (settings, protection, labels). */
  bootstrapped: boolean;
  /** Number of branch-protection rules / labels created, for the summary. */
  labelsCreated: number;
  protectionCreated: boolean;
  /** Committed secrets found in the imported code. */
  secretsFound: number;
  /** Security findings (vulnerable patterns) in the imported code. */
  securityIssues: number;
  /** Human-readable scan detail, already scrubbed by the scanner. */
  scanDetail: string;
  /** Set when this repo's onboarding failed; the others still ran. */
  error?: string;
}

export interface MigrationReport {
  repos: MigrationRepoReport[];
  /** Totals, precomputed so the view stays markup. */
  totalRepos: number;
  totalSecrets: number;
  totalSecurityIssues: number;
  reposBootstrapped: number;
  reposWithFindings: number;
}

/**
 * Bootstrap and scan every freshly imported repository.
 *
 * `repos` is the subset of a bulk import that actually landed — callers pass
 * only successful imports, since there is nothing to scan otherwise.
 */
export async function runMigrationOnboarding(
  repos: Array<{ id: string; owner: string; name: string; defaultBranch?: string | null }>,
  ownerUserId: string
): Promise<MigrationReport> {
  const results = await mapWithConcurrency(
    repos,
    DB_FANOUT_LIMIT,
    async (repo): Promise<MigrationRepoReport> => {
      const base: MigrationRepoReport = {
        owner: repo.owner,
        name: repo.name,
        bootstrapped: false,
        labelsCreated: 0,
        protectionCreated: false,
        secretsFound: 0,
        securityIssues: 0,
        scanDetail: "",
      };

      const branch = repo.defaultBranch || "main";

      // 1. Green defaults. Skips the welcome issue — an imported repo has
      //    real history and real issues; a "welcome" issue on top of 400
      //    migrated ones is noise, not onboarding.
      try {
        const { bootstrapRepository } = await import("./repo-bootstrap");
        const r = await bootstrapRepository({
          repositoryId: repo.id,
          ownerUserId,
          defaultBranch: branch,
          skipWelcomeIssue: true,
        });
        base.bootstrapped = true;
        base.labelsCreated = r.labelsCreated ?? 0;
        base.protectionCreated = r.protectionCreated ?? false;
      } catch (err) {
        base.error = `bootstrap: ${err instanceof Error ? err.message : String(err)}`;
      }

      // 2. The proof. Scan the code they just brought over.
      try {
        const { runSecretAndSecurityScan } = await import("./gate");
        const scan = await runSecretAndSecurityScan(
          repo.owner,
          repo.name,
          `refs/heads/${branch}`,
          branch,
          { scanSecrets: true, scanSecurity: true }
        );
        base.secretsFound = scan.secrets.length;
        base.securityIssues = scan.securityIssues.length;
        base.scanDetail = [scan.secretResult.details, scan.securityResult.details]
          .filter(Boolean)
          .join(" · ");
      } catch (err) {
        const msg = `scan: ${err instanceof Error ? err.message : String(err)}`;
        base.error = base.error ? `${base.error}; ${msg}` : msg;
      }

      return base;
    }
  );

  return {
    repos: results,
    totalRepos: results.length,
    totalSecrets: results.reduce((n, r) => n + r.secretsFound, 0),
    totalSecurityIssues: results.reduce((n, r) => n + r.securityIssues, 0),
    reposBootstrapped: results.filter((r) => r.bootstrapped).length,
    reposWithFindings: results.filter(
      (r) => r.secretsFound > 0 || r.securityIssues > 0
    ).length,
  };
}