diff --git a/README.md b/README.md index 21f79fb9..7d70cd01 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ When I heard Karpathy say this, I wanted to find out how. How does one person sh I'm [Garry Tan](https://x.com/garrytan), President & CEO of [Y Combinator](https://www.ycombinator.com/). I've worked with thousands of startups — Coinbase, Instacart, Rippling — when they were one or two people in a garage. Before YC, I was one of the first eng/PM/designers at Palantir, cofounded Posterous (sold to Twitter), and built Bookface, YC's internal social network. -**gstack is my answer.** I've been building products for twenty years, and right now I'm shipping more products than I ever have. In the last 60 days: 3 production services, 40+ shipped features, part-time, while running YC full-time. On logical code change — not raw LOC, which AI inflates — my 2026 run rate is **~700× my 2013 pace** (9,859 vs 14 logical lines/day). Year-to-date (through April 18), 2026 has already produced **207× the entire 2013 year**. Measured across 41 public + private `garrytan/*` repos including Bookface. AI wrote most of it. The point isn't who typed it, it's what shipped. +**gstack is my answer.** I've been building products for twenty years, and right now I'm shipping more products than I ever have. In the last 60 days: 3 production services, 40+ shipped features, part-time, while running YC full-time. On logical code change — not raw LOC, which AI inflates — my 2026 run rate is **~880× my 2013 pace** (12,382 vs 14 logical lines/day). Year-to-date (through April 18), 2026 has already produced **260× the entire 2013 year**. Measured across 41 public + private `garrytan/*` repos including Bookface. AI wrote most of it. The point isn't who typed it, it's what shipped. **2026 — 1,237 contributions and counting:** diff --git a/scripts/garry-output-comparison.ts b/scripts/garry-output-comparison.ts index 84785721..cbd756cf 100644 --- a/scripts/garry-output-comparison.ts +++ b/scripts/garry-output-comparison.ts @@ -46,6 +46,18 @@ type PerYearResult = { raw_lines_added: number; logical_lines_added: number; active_weeks: number; + days_elapsed: number; // 365 for past years; day-of-year for current year + is_partial: boolean; // true for current year (2026 today), false for past + per_day_rate: { // per calendar day (incl. non-active days) + logical: number; + raw: number; + commits: number; + }; + annualized_projection: { // per_day_rate × 365 — what the year looks like if pace holds + logical: number; + raw: number; + commits: number; + }; per_language: Record; caveats: string[]; }; @@ -55,9 +67,24 @@ type Output = { scc_available: boolean; years: PerYearResult[]; multiples: { - logical_lines_added: number | null; // 2026 / 2013 - commits_per_week: number | null; - raw_lines_added: number | null; + // TO-DATE: raw totals. Compares full 2013 year vs (possibly partial) 2026. + // Answers: "How much has been produced so far?" + to_date: { + logical_lines_added: number | null; + raw_lines_added: number | null; + commits: number | null; + files_touched: number | null; + }; + // RUN RATE: per-day pace, apples-to-apples regardless of calendar coverage. + // Answers: "What's the pace at, normalized for time elapsed?" + run_rate: { + logical_per_day: number | null; + raw_per_day: number | null; + commits_per_day: number | null; + }; + // Deprecated: kept for backwards-compat with older consumers reading the JSON. + // Aliases `to_date.logical_lines_added` — will be removed in a future version. + logical_lines_added: number | null; }; caveats_global: string[]; version: number; @@ -164,7 +191,25 @@ function analyzeCommit(commit: string, repoPath: string, sccAvailable: boolean): void sccAvailable; } -function analyzeRepo(repoPath: string, year: number, sccAvailable: boolean): PerYearResult { +/** + * Days elapsed in the given year as of `now`. For past years returns 365 + * (366 for leap years). For the current year returns the day-of-year + * through `now`. For future years returns 0. + */ +function daysElapsed(year: number, now: Date = new Date()): number { + const currentYear = now.getUTCFullYear(); + if (year < currentYear) { + const isLeap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + return isLeap ? 366 : 365; + } + if (year > currentYear) return 0; + // Current year: days since Jan 1 inclusive + const jan1 = new Date(Date.UTC(year, 0, 1)); + const diffMs = now.getTime() - jan1.getTime(); + return Math.max(1, Math.floor(diffMs / (24 * 60 * 60 * 1000)) + 1); +} + +function analyzeRepo(repoPath: string, year: number, sccAvailable: boolean, now: Date = new Date()): PerYearResult { const commits = enumerateCommits(year, repoPath); const perLang: Record = {}; let rawTotal = 0; @@ -199,6 +244,12 @@ function analyzeRepo(repoPath: string, year: number, sccAvailable: boolean): Per } } + const days = daysElapsed(year, now); + const isPartial = year === now.getUTCFullYear(); + const perDayLogical = days > 0 ? logicalTotal / days : 0; + const perDayRaw = days > 0 ? rawTotal / days : 0; + const perDayCommits = days > 0 ? commits.length / days : 0; + return { year, active: commits.length > 0, @@ -207,10 +258,22 @@ function analyzeRepo(repoPath: string, year: number, sccAvailable: boolean): Per raw_lines_added: rawTotal, logical_lines_added: logicalTotal, active_weeks: weeks.size, + days_elapsed: days, + is_partial: isPartial, + per_day_rate: { + logical: +perDayLogical.toFixed(2), + raw: +perDayRaw.toFixed(2), + commits: +perDayCommits.toFixed(3), + }, + annualized_projection: { + logical: Math.round(perDayLogical * 365), + raw: Math.round(perDayRaw * 365), + commits: Math.round(perDayCommits * 365), + }, per_language: perLang, caveats: commits.length === 0 ? [`No commits found for year ${year} in this repo with the configured email filter. If private work existed in this era, it is excluded.`] - : [], + : (isPartial ? [`Year ${year} is partial (day ${days} of 365). Run-rate multiple extrapolates current pace.`] : []), }; } @@ -229,20 +292,50 @@ function main() { // For V1, we analyze the single repo at repoRoot. Future work: enumerate // public garrytan/* repos via GitHub API + clone each into a cache dir. - const years = TARGET_YEARS.map(y => analyzeRepo(repoRoot, y, sccAvailable)); + const now = new Date(); + const years = TARGET_YEARS.map(y => analyzeRepo(repoRoot, y, sccAvailable, now)); const y2013 = years.find(y => y.year === 2013); const y2026 = years.find(y => y.year === 2026); - const multiples = { + + // Both multiples live in the same output — they measure different things: + // + // to_date = raw totals. "How much did 2026 produce so far?" + // (mixes full-year 2013 vs partial 2026; honest about volume) + // run_rate = per-day pace. "What's the throughput rate, normalized?" + // (apples-to-apples regardless of how much of 2026 has elapsed) + const toDate = { logical_lines_added: (y2013?.active && y2013.logical_lines_added > 0 && y2026?.active) ? +(y2026.logical_lines_added / y2013.logical_lines_added).toFixed(1) : null, - commits_per_week: (y2013?.active && y2013.active_weeks > 0 && y2026?.active && y2026.active_weeks > 0) - ? +((y2026.commits / y2026.active_weeks) / (y2013.commits / y2013.active_weeks)).toFixed(1) - : null, raw_lines_added: (y2013?.active && y2013.raw_lines_added > 0 && y2026?.active) ? +(y2026.raw_lines_added / y2013.raw_lines_added).toFixed(1) : null, + commits: (y2013?.active && y2013.commits > 0 && y2026?.active) + ? +(y2026.commits / y2013.commits).toFixed(1) + : null, + files_touched: (y2013?.active && y2013.files_touched > 0 && y2026?.active) + ? +(y2026.files_touched / y2013.files_touched).toFixed(1) + : null, + }; + + const runRate = { + logical_per_day: (y2013?.per_day_rate.logical && y2013.per_day_rate.logical > 0 && y2026?.active) + ? +(y2026.per_day_rate.logical / y2013.per_day_rate.logical).toFixed(1) + : null, + raw_per_day: (y2013?.per_day_rate.raw && y2013.per_day_rate.raw > 0 && y2026?.active) + ? +(y2026.per_day_rate.raw / y2013.per_day_rate.raw).toFixed(1) + : null, + commits_per_day: (y2013?.per_day_rate.commits && y2013.per_day_rate.commits > 0 && y2026?.active) + ? +(y2026.per_day_rate.commits / y2013.per_day_rate.commits).toFixed(1) + : null, + }; + + const multiples = { + to_date: toDate, + run_rate: runRate, + // Back-compat alias — older consumers read `multiples.logical_lines_added`. + logical_lines_added: toDate.logical_lines_added, }; const output: Output = { @@ -268,11 +361,23 @@ function main() { fs.writeFileSync(outPath, JSON.stringify(output, null, 2) + '\n'); process.stderr.write(`Wrote ${outPath}\n`); - process.stderr.write(`2013 logical added: ${y2013?.logical_lines_added ?? 'n/a'} | 2026 logical added: ${y2026?.logical_lines_added ?? 'n/a'}\n`); - if (multiples.logical_lines_added !== null) { - process.stderr.write(`Logical-lines multiple: ${multiples.logical_lines_added}× (2026 / 2013)\n`); - } else { - process.stderr.write(`Logical-lines multiple: not computable (one or both years inactive in this repo).\n`); + process.stderr.write( + `2013: ${y2013?.logical_lines_added ?? 'n/a'} logical added (${y2013?.days_elapsed ?? '?'}d) | ` + + `2026: ${y2026?.logical_lines_added ?? 'n/a'} logical added (${y2026?.days_elapsed ?? '?'}d, ${y2026?.is_partial ? 'partial' : 'full'})\n` + ); + if (toDate.logical_lines_added !== null) { + process.stderr.write(`TO-DATE multiple (raw volume): ${toDate.logical_lines_added}× logical, ${toDate.raw_lines_added}× raw\n`); + } + if (runRate.logical_per_day !== null) { + process.stderr.write( + `RUN-RATE multiple (per-day pace): ${runRate.logical_per_day}× logical/day, ${runRate.commits_per_day}× commits/day\n` + + ` 2013 pace: ${y2013?.per_day_rate.logical.toFixed(1) ?? '?'} logical/day | ` + + `2026 pace: ${y2026?.per_day_rate.logical.toFixed(1) ?? '?'} logical/day | ` + + `2026 annualized: ${y2026?.annualized_projection.logical.toLocaleString() ?? '?'} logical/year projected\n` + ); + } + if (toDate.logical_lines_added === null && runRate.logical_per_day === null) { + process.stderr.write(`No multiple computable (one or both years inactive in this repo).\n`); } }