Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit53d32beunknown_key

docs(L): record BLOCK L (10 sub-blocks) in BUILD_BIBLE.md

docs(L): record BLOCK L (10 sub-blocks) in BUILD_BIBLE.md

Per §6 session workflow, recording the marketing/acquisition/activation/
retention package on PR #62. All strictly additive — no locked entry
deleted or renamed.

  • §3 — added BLOCK L section with the funnel (Hook / Mouth / Taste)
    overview and ✅-shipped entries for L1 through L10, each with the
    actual files, test counts, and key behaviours so future agents
    can reason about them without re-reading every source file.
  • §4.1 — locked migrations 0041 (sleep_mode columns on users) and
    0042 (github seed row in sso_config). Both additive.
  • §4.4 AI layer — locked sleep-mode.ts (L1), ai-hours-saved.ts (L9),
    public-stats.ts (L4) as the AI-derived modules.
  • §4.5 Platform — bumped autopilot row to enumerate the 9th task
    (sleep-mode-digest), and locked demo-activity.ts +
    demo-activity-seed.ts (L3) as load-bearing demo plumbing.
  • §5.1 — bumped the test-count claim from 1350 / 0 (post-K) to
    1491 / 0 / 2 (post-L). +218 tests vs the 1273 pre-K baseline.
  • §7 IN-FLIGHT — added a 10-item L follow-ups list alongside the
    existing K list. None blocking; suite is 1491/0/2 green.

The 10 L follow-ups span the things that are worth a follow-up commit
but didn't block the funnel landing — formula consolidation, column
splits, locked-contract §4.7 enumeration, stale-handler cleanup
in marketing.tsx, etc.
Claude committed on May 13, 2026Parent: 5f2e749
1 file changed+39253d32be8c12a1dda6d99bcf89c21b4ae0fbfc604
1 changed file+39−2
ModifiedBUILD_BIBLE.md+39−2View fileUnifiedSplit
334334- **K2** — AI-gated auto-merge → ✅ shipped. `src/lib/auto-merge.ts` (399 lines), `drizzle/0040_branch_protection_auto_merge.sql`, settings checkbox on `src/routes/gates.tsx`. Pure `decideAutoMerge(facts)` + DB orchestrator `evaluateAutoMerge(ctx, opts?)`. Decision rules (all must hold): matching branch_protection rule with `enable_auto_merge=true`, PR not draft, manual-merge `evaluateProtection` passes, AI approval per `aiCommentLooksApproved` when `requireAiApproval=true`, optional size cap. 19 tests covering each block reason + the `blocking-non-empty iff merge=false` invariant. Side-effect-free; only K3 calls it.
335335- **K3** — AI-driven autopilot → ✅ shipped. Two new tick tasks on the locked autopilot ticker: `auto-merge-sweep` (calls K2 every tick, performs merges via `performMerge`, audits `auto_merge.evaluated`/`auto_merge.merged`/`auto_merge.merge_failed`, posts `<!-- gluecron:auto-merge:v1 -->` marker) and `ai-build-from-issues` (open issues labelled `ai:build` → spec-to-PR dispatch, posts `<!-- gluecron:ai-build:v1 -->` marker). Shared `src/lib/pr-merge.ts performMerge(prId)` factored from `routes/pulls.tsx`. `src/lib/ai-build-tasks.ts runAiBuildTaskOnce(deps)` orchestrator with 5 DI'd collaborators. 14 tests covering draft skip, archived skip, marker dedup, AUTOPILOT_DISABLED short-circuit, AI-key absence short-circuit, and "merge fn called exactly once per merge=true PR".
336336
337### BLOCK L — Hook / mouth / taste — packaging the Claude-native pitch
338The marketing + acquisition + activation + retention package. Ten sub-blocks shipped together (PR #62) on 2026-05-13. The whole funnel:
339 - **Hook** (acquisition surface): L4 social-proof counters · L5 /vs-github · L10 hero · L8 /pricing
340 - **Mouth** (activation): L2 one-command install · L6 GitHub OIDC · L7 Claude Code skills · L3 live /demo
341 - **Taste** (retention): L1 Sleep Mode (marquee) · L9 AI hours saved counter
342
343- **L1** — Sleep Mode → ✅ shipped. Marquee feature. User toggle + UTC hour preference (`drizzle/0041_sleep_mode.sql` adds `users.sleep_mode_enabled` + `users.sleep_mode_digest_hour_utc`). `src/lib/sleep-mode.ts` (536 lines) composes a "while-you-slept" daily digest of K3 autopilot work (auto-merges, ai:build dispatches, AI reviews, secret-scan fixes). New autopilot task `sleep-mode-digest` fires daily per user's preferred UTC hour with 23h cooldown. Public `/sleep-mode` marketing page. Settings UI in `src/routes/settings.tsx` + "Preview digest now" button at `/settings/sleep-mode/preview`. Re-uses `email-digest.ts` escape helpers + the `sendEmail()` provider. 17 tests.
344- **L2** — One-command install → ✅ shipped. `curl -sSL gluecron.com/install | bash` does the whole onboarding in one shot: signs in, mints a fresh `glc_` PAT (admin scope), writes Claude Desktop's `claude_desktop_config.json` with a `gluecron` MCP server entry, optionally imports the user's current GitHub repo, mkdirs `~/.claude/skills/` and drops L7's skill files. `scripts/install.sh` (303 lines). New endpoint `POST /api/v2/auth/install-token` (session-cookie-only auth; explicit 401 on Bearer to prevent token escalation; audits `auth.install_token.created`). Public `GET /install` serves the script (`Cache-Control: public, max-age=300`). 11 tests (9 + 2 DB-gated skip).
345- **L3** — Live /demo page → ✅ shipped. Anonymous visitors land on `/demo` and watch K3 autopilot work in real time. Three tiles refreshing every 30s: queued ai:build issues, recent auto-merges, AI reviews today. Live activity feed pinned at the bottom. `src/routes/demo.tsx` (409 lines) SSR + plain-JS poller. `src/lib/demo-activity.ts` (510 lines) — pure helpers (`listQueuedAiBuildIssues`, `listRecentAutoMerges`, `listRecentAiReviews`, `listDemoActivityFeed`), all scoped to demo-owned repos, never throw, 30s LRU cache. `src/lib/demo-activity-seed.ts` (additive sibling to the locked `demo-seed.ts`, called from `src/index.ts`) adds 1-2 `ai:build`-labelled issues per demo repo + an open and a merged PR + an `AI_REVIEW_MARKER` comment + an `auto_merge.merged` audit row so the tiles are populated. Four JSON endpoints `GET /api/v2/demo/{activity,queued,merges,reviews}`. 15 tests.
346- **L4** — Public stats counters → ✅ shipped. `src/lib/public-stats.ts` (363 lines) computes 9 site-wide metrics over PUBLIC data only (every counter JOINs on `is_private=false` so private repos never leak). 5-min LRU cache. `emptyPublicStats()` zero-fallback on DB error. Re-uses `computeHoursSaved` from L9. New endpoint `GET /api/v2/stats` (`Cache-Control: public, max-age=300`). Landing page renders six animated count-up tiles after the hero (IntersectionObserver + `prefers-reduced-motion` fallback; static numbers stay in HTML for no-JS clients). 13 tests.
347- **L5** — /vs-github comparison page → ✅ shipped. `src/routes/vs-github.tsx` (660 lines) — public marketing page at `GET /vs-github`. Hero + 26-row honest comparison table grouped into 4 categories (AI-native workflow, developer integration, hosting+workflow, pricing) + "killer move" Sleep Mode banner + 4-question objection FAQ + dual-CTA footer (Migrate from GitHub / Try the demo). Removed a stale shadow handler in `marketing.tsx` that was overlapping the URL. The legacy branch-diff `/:owner/:repo/compare/:spec?` route is untouched. 6 tests covering anon 200, content, all 10 AI-native rows, CTA hrefs, and the legacy route guard.
348- **L6** — Sign in with GitHub → ✅ shipped. Reuses the I10 SSO machinery. `drizzle/0042_github_oauth.sql` seeds a new row id='github' in the existing `sso_config` table — schema unchanged. `src/lib/github-oauth.ts` (187 lines) — pure network-only helpers (`buildGithubAuthorizeUrl`, `exchangeGithubCode`, `fetchGithubUserinfo`, `fetchGithubPrimaryEmail`) handling GitHub's non-OIDC quirks: `Accept: application/json` on token endpoint, null email fallback via `/user/emails` primary+verified pick, no id_token. Subject keyed as `github:${id}` to coexist with enterprise SSO. New routes `/login/github`, `/login/github/callback`, `/admin/github-oauth`. "Sign in with GitHub" button on `/login` above the enterprise SSO option. 22 tests.
349- **L7** — Claude Code skill bundle → ✅ shipped. Three skills shipped at the project root (`.claude/skills/`) and copied to `~/.claude/skills/` by the L2 install script: `gluecron-pr` (open / list / fetch / comment / merge / close PRs), `gluecron-issue` (create / list / comment / close / reopen issues), `gluecron-review` (act as a secondary AI reviewer on top of Gluecron's built-in Sonnet 4 review). Each SKILL.md has YAML frontmatter that names "Gluecron" so the Claude Code harness auto-invokes when the active repo's origin URL matches a Gluecron host. CLAUDE.md updated with a "Skills available for this project" section. 25 tests.
350- **L8** — Free-tier UI polish → ✅ shipped. Pure UX over the existing F4 billing infrastructure; zero new billing logic. `src/routes/pricing.tsx` (641 lines) — public `GET /pricing`. Hero ("Free for the AI-curious. Pay only when you're ready to scale.") + four plan cards driven by `FALLBACK_PLANS` (Free/Pro/Team/Enterprise) with CTA to `/settings/billing?plan=` (or `/register?next=...` for anon). "What you get on the free tier" 13-feature 2-col block. Self-host vs Gluecron Cloud comparison. 5-question FAQ. `src/routes/billing.tsx` extended with a "this month" usage panel + "Upgrade to Pro" CTA for Free users + a "Detailed plan comparison" link to `/pricing`. Small "Free forever for the AI-curious" link added near the landing hero. Plan card text references K1/K2/K3/L1/L7/L9 features explicitly so visitors see what they get on Free.
351- **L9** — AI hours saved counter → ✅ shipped. Retention feature. `src/lib/ai-hours-saved.ts` (413 lines) exports `computeHoursSaved(breakdown)` (pure, deterministic, used by L1 and L4 too) + `computeAiSavingsForUser(userId, opts?)` + `computeLifetimeAiSavingsForUser(userId)`. Formula: `prsAutoMerged*0.30 + issuesBuiltByAi*1.50 + aiReviewsPosted*0.25 + aiTriagesPosted*0.10 + aiCommitMsgs*0.05 + secretsAutoRepaired*0.50 + gateAutoRepairs*0.40` — heuristic constants documented inline. Dashboard renders a large gradient hero widget with this-week / all-time tabs + breakdown pills + "How is this calculated?" disclosure. New endpoint `GET /api/v2/me/ai-savings` so VS Code + CLI consume the same metric. 17 tests.
352- **L10** — Marketing landing hero rewrite → ✅ shipped. `src/views/landing.tsx` — new gradient headline ("The git host built around Claude."), one-sentence subhead ("Label an issue. Walk away. Wake up to a merged PR."), one-line install snippet + copy button, three CTAs (`/register` / `/demo` / `/vs-github`), "what just happened" rail driven by L4's `publicStats`, "Three reasons to switch" 3-column section linking to Sleep Mode / `/import` / `/demo`, and a "How is this different from GitHub?" pull-quote linking to `/vs-github`. Preserves L4 counter tiles + L5 vs-github CTA + L7 git-remote caption + L8 free-tier link. `src/views/layout.tsx` extended additively with optional `description`/`ogTitle`/`ogDescription`/`ogType`/`twitterCard`/`fullTitle` props (render byte-identical when omitted, so the §4.7 locked contract holds). `src/routes/web.tsx` landing handler now passes SEO + OG props. 9 tests.
353
337354---
338355
339356## 4. LOCKED BLOCKS (DO NOT UNDO)
384401- `drizzle/0037_workflow_engine_v2.sql` — migration, never edited in place. Strictly additive to Block C1 (`drizzle/0008_workflows.sql` stays locked). Adds `workflow_secrets` (AES-256-GCM, `(repo, name)` unique), `workflow_dispatch_inputs` (per-workflow input schema), `workflow_run_cache` (content-addressable cache, repo/branch/tag scoped), `workflow_runner_pool` (warm-runner registry).
385402- `drizzle/0038_deployment_ready_after.sql` — migration, never edited in place. Adds `deployments.ready_after` (timestamptz, NULL = no wait) + partial index. Backs the wait-timer enforcement for environment approvals (was previously a stub per Block C4 v1).
386403- `drizzle/0040_branch_protection_auto_merge.sql` (Block K2) — migration, never edited in place. Adds `branch_protection.enable_auto_merge` (boolean, default false) — strict opt-in so the K3 autopilot sweep is default-deny per branch. Slot 0040 (not 0039) because `drizzle/0039_repair_flywheel.sql` was already taken; additive only, never replaces an earlier migration.
404- `drizzle/0041_sleep_mode.sql` (Block L1) — migration, never edited in place. Adds `users.sleep_mode_enabled` (boolean, default false) + `users.sleep_mode_digest_hour_utc` (integer 0-23, default 9). Strictly additive.
405- `drizzle/0042_github_oauth.sql` (Block L6) — migration, never edited in place. INSERTs a seed row keyed `id='github'` into the existing `sso_config` table so the L6 GitHub-OIDC flow can coexist with the I10 enterprise-OIDC singleton. Schema unchanged; pure seed.
387406
388407### 4.2 Git layer (locked)
389408- `src/git/repository.ts` — tree / blob / commits / diff / branches / blame / search / raw / tags / commitsBetween
439458- `src/lib/auto-merge.ts` (Block K2) — pure decision helper `decideAutoMerge(facts)` plus the DB orchestrator `evaluateAutoMerge(ctx, opts?)`. Default-deny: requires a matching `branch_protection` rule with `enable_auto_merge=true` AND every gate the manual merge path enforces (reuses `matchProtection` / `evaluateProtection` / `listRequiredChecks` / `passingCheckNames` from §4.4's branch-protection module). When `requireAiApproval=true`, demands an AI-review comment carrying `AI_REVIEW_MARKER` that survives `aiCommentLooksApproved` (rejects "AI review unavailable" / "severity: blocking" / "flagged N item(s)"). Optional size cap (`maxChangedFiles` / `maxChangedLines`). Side-effect-free; K3's autopilot is the only intended caller. Also exports `recordAutoMergeAttempt(repoId, prId, decision)` for the audit trail.
440459- `src/lib/pr-merge.ts` (Block K3) — shared `performMerge(prId)` factored from `src/routes/pulls.tsx`. Pure `{ok, error}` return; never throws. Handles git-update-ref / `mergeWithAutoResolve` switch + PR state flip + J7 close-keyword auto-close. The HTTP route still has its own inline merge mechanics — refactoring it to call `performMerge()` is tracked as a follow-up. Both code paths are kept in lock-step until that refactor.
441460- `src/lib/ai-build-tasks.ts` (Block K3) — `runAiBuildTaskOnce(deps)` orchestrator + 5 collaborator interfaces (`findIssues`, `listOpenPrs`, `postIssueComment`, `buildSpec`, `dispatchSpec`). Finds open issues labelled `ai:build` (case-insensitive), skips those already linked to a PR via J7 close-keywords or already dispatched (marker `<!-- gluecron:ai-build:v1 -->`), composes a spec via `buildSpecFromIssue` (from `src/routes/specs.tsx`), dispatches to `src/lib/spec-to-pr.ts`. Posts the marker on the issue so re-runs never re-dispatch. Cap 20 issues per tick. Fire-and-forget; spec-to-pr failures caught + logged.
461- `src/lib/sleep-mode.ts` (Block L1) — `composeSleepModeReport(userId, opts?)` + `renderSleepModeDigest(report, opts)` + `sendSleepModeDigestForUser(userId)`. Reads K3 audit-trail rows in the last N hours and produces the "while-you-slept" daily digest. Re-uses `email-digest.ts __internal.escapeHtml` + the shared `sendEmail()` provider. Never throws — DB error returns a zero-activity report. Used by the `sleep-mode-digest` autopilot task and the `/settings/sleep-mode/preview` endpoint.
462- `src/lib/ai-hours-saved.ts` (Block L9) — pure `computeHoursSaved(breakdown)` (the canonical formula — L1 and L4 both import it) + `computeAiSavingsForUser(userId, opts?)` + `computeLifetimeAiSavingsForUser(userId)`. Formula constants documented inline. Counts AI-driven events (auto_merge.merged, ai_build.dispatched, pr_comments.is_ai_review, gate_runs.status='repaired') over a window; conservative weighted sum; rounds to 1 decimal. Never throws — DB error returns all-zero report.
463- `src/lib/public-stats.ts` (Block L4) — `computePublicStats(opts?)` orchestrator + 9 default DB-backed counters. EVERY counter JOINs on `repositories.is_private=false` — private repos must never leak into landing-page social proof. 5-minute LRU cache via `src/lib/cache.ts`. `emptyPublicStats()` zero-fallback. Re-uses `computeHoursSaved` from L9 for the `weeklyHoursSaved` field. Never throws.
442464
443465### 4.5 Platform (locked)
444466- `src/lib/notify.ts` — notification creation + audit log (swallow-failures pattern). Also fans out email to opted-in recipients for `mention|review_requested|assigned|gate_failed`. Exports `__internal` for tests.
452474- `src/lib/observability.ts` — minimal dependency-free observability layer. Wired into `app.onError`. Sinks: `ERROR_WEBHOOK_URL` and/or `SENTRY_DSN`. Never throws.
453475- `src/lib/sse.ts` — in-process topic-based pub/sub broadcaster for Server-Sent Events. Backs the live UI updates layer.
454476- `src/lib/sse-client.ts` — emits a `<script>`-droppable JS snippet for SSR'd views to subscribe to a topic. Plain JS only — no client framework.
455- `src/lib/autopilot.ts` — 5-minute self-sufficiency ticker. Default tasks (in order): `mirror-sync`, `merge-queue`, `weekly-digest`, `advisory-rescan`, `wait-timer-release`, `scheduled-workflows`, `auto-merge-sweep` (Block K3), `ai-build-from-issues` (Block K3). `AUTOPILOT_DISABLED=1` opt-out. Observability via `getLastTick()` + `getTickCount()`. K3-added tasks: `auto-merge-sweep` calls `evaluateAutoMerge` (§4.4) on every open non-draft PR updated in the last 24h (cap 50), performs the merge via `pr-merge.ts performMerge` on `merge:true` decisions, and emits `auto_merge.evaluated`/`auto_merge.merged`/`auto_merge.merge_failed` audit rows plus a stable `<!-- gluecron:auto-merge:v1 -->` PR comment. `ai-build-from-issues` runs the orchestrator in `ai-build-tasks.ts`.
477- `src/lib/autopilot.ts` — 5-minute self-sufficiency ticker. Default tasks (in order): `mirror-sync`, `merge-queue`, `weekly-digest`, `advisory-rescan`, `wait-timer-release`, `scheduled-workflows`, `auto-merge-sweep` (Block K3), `ai-build-from-issues` (Block K3), `sleep-mode-digest` (Block L1). `AUTOPILOT_DISABLED=1` opt-out. Observability via `getLastTick()` + `getTickCount()`. K3 tasks: `auto-merge-sweep` calls `evaluateAutoMerge` (§4.4) on every open non-draft PR updated in the last 24h (cap 50), performs the merge via `pr-merge.ts performMerge` on `merge:true` decisions, and emits `auto_merge.evaluated`/`auto_merge.merged`/`auto_merge.merge_failed` audit rows plus a stable `<!-- gluecron:auto-merge:v1 -->` PR comment. `ai-build-from-issues` runs the orchestrator in `ai-build-tasks.ts`. L1 task: `sleep-mode-digest` finds users with `sleep_mode_enabled=true` whose preferred UTC hour matches the current hour AND who haven't received a digest in 23h, sends each one a `sleep-mode.ts`-composed daily report. Cap 100 users per tick.
478- `src/lib/demo-activity.ts` (Block L3) — pure helpers (`listQueuedAiBuildIssues`, `listRecentAutoMerges`, `listRecentAiReviews`, `countAiReviewsSince`, `listDemoActivityFeed`). All scoped to `demo`-owned repos. Never throw — DB error returns `[]`. 30-second LRU cache per query.
479- `src/lib/demo-activity-seed.ts` (Block L3) — `ensureDemoActivity()` additive sibling to the locked `demo-seed.ts`. Idempotent. Adds an `ai:build`-labelled issue per demo repo + an open and a merged PR on `todo-api` + an `AI_REVIEW_MARKER`-bearing pr_comment + an `auto_merge.merged` audit row, so the live `/demo` tiles render with content on a fresh DB. Boot path wired in `src/index.ts` (only the lib file is locked; index.ts is the legitimate caller surface).
456480- `src/lib/demo-seed.ts` — idempotent `ensureDemoContent()` (creates `demo` user + `hello-python` / `todo-api` / `design-docs`). `DEMO_SEED_ON_BOOT=1` boot flag in `src/index.ts`.
457481- `src/lib/import-helper.ts` — small helpers for the GitHub import flow (`src/routes/import.tsx`).
458482- `src/lib/import-verify.ts` — post-migration smoke verifier (object count, branches, default-branch HEAD). Re-runnable from `src/routes/migrations.tsx`.
612636```bash
613637bun install
614638bun dev # hot reload
615bun test # 1350 tests currently pass (0 failed) as of 2026-05-13 after K1/K2/K3 — run `bun install` first
639bun test # 1491 tests currently pass (0 failed, 2 skipped) as of 2026-05-13 after BLOCK K (3 sub-blocks) + BLOCK L (10 sub-blocks). +218 tests vs the 1273 pre-K baseline. Run `bun install` first.
616640bun run db:migrate
617641```
618642
661685
662686(Intentionally empty. Add here if a block is partially complete at session end.)
663687
688**2026-05-13 — BLOCK L (PR #62) follow-ups (all non-blocking; suite is 1491/0/2 green):**
689
6901. L1 and L9 ship near-identical hours-saved formulae. L1's `sleep-mode.ts` has its own copy; L9's `ai-hours-saved.ts` exports the canonical `computeHoursSaved`. L4 already imports L9's; L1 should too. One-line cleanup.
6912. L1's 23h cooldown uses `users.last_digest_sent_at`, which is shared with the existing weekly digest (I7). A user with both opted in could starve one feed of the other. Worth splitting columns once an interaction is reported.
6923. L10 added 5 optional props (`description`, `ogTitle`, `ogDescription`, `ogType`, `twitterCard`, `fullTitle`) to `Layout`. Strictly additive (render byte-identical when omitted) so the §4.7 locked contract holds — but the §4.7 entry should be bumped to enumerate the new props on the next reconciliation pass.
6934. L5 removed a stale pre-existing `/vs-github` handler from `src/routes/marketing.tsx` (lines 1164-1647 of the old file). Confirmed no test asserted on the removed block; sanity-check at next release if anything trips on the deleted CSS class names.
6945. L4's secrets-fixed counter uses `gate_runs.status='repaired' AND gate_name ILIKE '%secret%'`; L9's counter uses the same convention. If either moves, both need migrating together.
6956. L4 accepts both `success` and `succeeded` for `deployments.status` until the schema convention normalises.
6967. L7's `scripts/install.sh` writes SKILL.md bodies via inline heredocs which can drift from the canonical `.claude/skills/` files. Worth a build step that materialises them at release time.
6978. L7 bundles the skill files only into THIS repo + L2-installed users. Imported repos don't get them yet (skip flagged in the L7 spec — bundling-into-imports is a future block).
6989. L9's `ai.commit_message.generated` audit action isn't currently emitted by any producer; counter returns 0 for that line item until `src/lib/ai-generators.ts generateCommitMessage` gets an `audit()` call.
69910. L6's `/login/sso/unlink` deletes any sso_user_links row including the GitHub one. A dedicated `/settings/github/unlink` route would be clearer UX.
700
664701**2026-05-13 — BLOCK K (PR #62) follow-ups (all four are non-blocking; the suite is 1350/0 green):**
665702
6667031. Refactor `src/routes/pulls.tsx` merge handler to call `src/lib/pr-merge.ts performMerge()`. K3 factored the helper but left the route's inline merge mechanics untouched because the route's `c.redirect(?error=…)` flow doesn't map cleanly to the helper's `{ok,error}` return — and cracking open the locked route risked altering user-facing redirect semantics. The two paths are currently lock-step duplicates. Single shared code path is the goal.
667704