Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

BUILD_BIBLE.md

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

BUILD_BIBLE.mdBlame644 lines · 1 contributor
9ab6971Claude1# GLUECRON BUILD BIBLE
2
3**This file is the single source of truth for the GlueCron build.**
4
5**Every Claude agent MUST read this file in full before touching code. No exceptions.**
6
7GlueCron is a GitHub replacement — AI-native code intelligence, green ecosystem enforcement, git hosting, automated CI. It is production infrastructure for multiple downstream platforms. Production cannot stop.
8
9---
10
11## 1. AGENT POLICY (READ FIRST, FOLLOW ALWAYS)
12
13### 1.1 Required reads at session start
141. `BUILD_BIBLE.md` (this file) — complete
152. `CLAUDE.md` — stack + architecture
163. `README.md` — user-facing overview
174. Most recent commit on the current branch (`git log -1 --stat`)
18
19### 1.2 Do-not-undo rule
20- Anything listed in **§4 LOCKED BLOCKS** is shipped and must not be deleted, renamed, or semantically altered without the owner's explicit written permission in the current session.
21- "Refactor" is not permission. "Clean up" is not permission. "Simplify" is not permission.
22- If a locked file seems wrong, open an issue in the plan and keep going on a new block.
23
24### 1.3 Continuous-build rule
25- The owner runs many parallel projects. Do not stop work to ask for clarification that can be inferred from this file.
26- Default behaviour when a block is partially complete: **finish it, run tests, commit, push, start the next block**.
27- Only stop for genuinely blocking decisions: destructive operations, architectural reversals, requests outside this plan, or repeated test failures you can't diagnose.
28- Never stop because "the session might run out." Commit what works and keep building.
29
30### 1.4 Branch + commit rules
31- Development branch: whatever the current session was told (check session opening message). Fall back to `main` if none given.
32- One commit per completed block. Message format: `feat(BLOCK-ID): <summary>`.
33- Push after every commit with `git push -u origin <branch>`.
34- Never force-push. Never `--no-verify`. Never amend published commits.
35
36### 1.5 Quality bars (non-negotiable)
37- `bun test` must pass before every commit.
38- New features ship with tests in `src/__tests__/`.
39- New routes use `softAuth` or `requireAuth` middleware.
40- New DB tables have a corresponding migration in `drizzle/`.
41- AI features use `isAiAvailable()` guards and degrade gracefully without `ANTHROPIC_API_KEY`.
42- Every user-facing failure mode has a fallback — no 500s reach the UI.
43
44### 1.6 Green-ecosystem-by-default
45- Every new repo auto-configures: gates on, branch protection on, labels seeded, CODEOWNERS synced, welcome issue posted.
46- Users can opt out per feature but defaults are maximum-green.
47- Nothing broken ever reaches production, the website, or the customer.
48
15e65d2Claude49### 1.7 Parallelism rule (added per owner request)
50- **Default to spawning sub-agents whenever work can be parallelised.** Owner-cost of an idle main thread is high; owner-cost of an extra agent is near-zero.
51- Independent files = parallel agents. Schema-only edits, new route files, doc updates, test additions, codebase research — all of these run in parallel by default unless they collide.
52- Coordinate file ownership: one agent per file. Never let two agents edit the same file. Mounting + middleware integration stay on the main thread to avoid merge conflicts.
53- When launching multiple agents, send them in a single message with multiple Agent tool calls so they actually run concurrently.
54- The main thread is responsible for: reviewing each agent's output before integrating, running the test suite, and committing. Trust-but-verify — read the changes, don't just rely on the agent's summary.
55
9ab6971Claude56---
57
58## 2. GITHUB PARITY SCORECARD
59
60Legend: ✅ shipped · 🟡 partial · ❌ not built
61
62### 2.1 Repository hosting
63| Feature | Status | Notes |
64|---|---|---|
65| Git Smart HTTP (clone / push / fetch) | ✅ | `src/routes/git.ts`, `src/git/protocol.ts` |
66| SSH keys | ✅ | `ssh_keys` table, `src/routes/settings.tsx` |
67| Public / private visibility | ✅ | `repositories.isPrivate` |
68| Forking | ✅ | `src/routes/fork.ts` |
69| Stars | ✅ | `stars` table, `/:owner/:repo/star` |
70| Topics | ✅ | `repo_topics` table |
71cd5ecClaude71| Archive / disable repo | ✅ | I1 — `src/routes/repo-settings.tsx` archive toggle; `RepoHeader` renders an "Archived" badge when `is_archived=true`. |
72| Repository transfer | ✅ | I3 — `src/routes/repo-settings.tsx` transfer form + `POST /:owner/:repo/settings/transfer`; ownership change recorded in `repo_transfers` audit table. Reject conflicts (target owner already has a repo by that name) with a redirect. |
73| Template repositories | ✅ | I2 — `drizzle/0022_repo_templates.sql` adds `is_template`. `src/routes/templates.ts` serves `POST /:owner/:repo/use-template` (git clone --bare into caller's namespace). "Use this template" CTA rendered on the public repo page. |
4a0dea1Claude74| Repository mirroring | ✅ | I9 — pull-style mirror of an upstream git URL. `drizzle/0026_repo_mirrors.sql` adds `repo_mirrors` (one-per-repo config) + `repo_mirror_runs` (audit log). `src/lib/mirrors.ts` validates URLs (https/http/git only, rejects ssh/file/shell metacharacters), runs `git fetch --prune --tags` via `Bun.spawn` with a 5-min timeout + `GIT_TERMINAL_PROMPT=0`. `src/routes/mirrors.tsx` exposes `/:owner/:repo/settings/mirror` + `/admin/mirrors/sync-all`. |
9ab6971Claude75
76### 2.2 Code browsing
77| Feature | Status | Notes |
78|---|---|---|
79| File tree browser | ✅ | `src/routes/web.tsx` |
80| Syntax highlighting | ✅ | 40+ languages, `src/lib/highlight.ts` |
81| Commit history | ✅ | |
82| Diffs | ✅ | |
83| Blame | ✅ | |
84| Raw file download | ✅ | |
85| Branch switcher | ✅ | |
86| Tag listing | ✅ | new this build |
87| Code search (ILIKE) | ✅ | per-repo + global |
3cbe3d6Claude88| Semantic / embedding search | ✅ | D1 — `code_chunks` table + lexical fallback, optional Voyage `voyage-code-3`; `src/lib/semantic-search.ts`, `src/routes/semantic-search.tsx` |
4c8f666Claude89| Symbol / xref navigation | ✅ | I8 — `src/lib/symbols.ts` regex-based extractor for ts/js/py/rs/go/rb/java/kt/swift; on-demand indexer persists top-level definitions into `code_symbols` (0025). `src/routes/symbols.tsx` serves `/:owner/:repo/symbols` overview + A–Z list, `/:owner/:repo/symbols/search?q=` prefix search, `/:owner/:repo/symbols/:name` definition detail. Owner-only reindex. |
8098672Claude90| Dependency graph | ✅ | J1 — `src/lib/deps.ts` parses package.json / requirements.txt / pyproject.toml / go.mod / Cargo.toml / Gemfile / composer.json without a TOML lib. `src/routes/deps.tsx` serves `/:owner/:repo/dependencies` grouped by ecosystem with per-ecosystem counts; owner-only reindex walks the default-branch tree (max 200 manifests, 1MB each). `drizzle/0028_repo_dependencies.sql` adds `repo_dependencies`. |
f60ccdeClaude91| Security advisories / Dependabot alerts | ✅ | J2 — curated 12-entry seed list + minimal semver range matcher cross-referenced against J1 dep rows. `src/lib/advisories.ts` + `src/routes/advisories.tsx` serve `/:owner/:repo/security/advisories` (open) + `/all`, owner-only `POST /scan`, and per-alert dismiss/reopen. `drizzle/0029_security_advisories.sql` adds `security_advisories` + `repo_advisory_alerts`. |
3951454Claude92| Commit signature verification (Verified badge) | ✅ | J3 — GPG + SSH pubkey registration at `/settings/signing-keys`, `gpgsig` extraction from raw commit objects, OpenPGP packet walker for Issuer Fingerprint, SHA-256 fingerprints for SSHSIG pubkeys, memoised in `commit_verifications`. Green "Verified" badge rendered on commit list + detail when a registered key matches. `src/lib/signatures.ts` + `src/routes/signing-keys.tsx` + `drizzle/0030_signing_keys.sql`. |
9ff7128Claude93| Repository rulesets (push policy engine) | ✅ | J6 — named policies group N rules at active/evaluate/disabled enforcement. Pure evaluator `evaluatePush(rulesets, ctx)` → `{allowed, violations}`. Six rule types: commit_message_pattern, branch_name_pattern, tag_name_pattern, blocked_file_paths, max_file_size, forbid_force_push. Glob-lite matcher (`*` = non-slash, `**` = anything). Owner-only CRUD at `/:owner/:repo/settings/rulesets`. `src/lib/rulesets.ts` + `src/routes/rulesets.tsx` + `drizzle/0032_repo_rulesets.sql`. |
0cdfd89Claude94| Commit status API (external CI signals) | ✅ | J8 — external systems POST per-commit (sha, context) statuses with state pending/success/failure/error. Combined rollup reduces to worst state. Public list + combined endpoints; write requires owner auth. Rendered on commit detail view as a pill row. `src/lib/commit-statuses.ts` + `src/routes/commit-statuses.ts` + `drizzle/0033_commit_statuses.sql`. |
3648956Claude95| Repo status badges (shields.io SVG) | ✅ | J10 — embeddable `image/svg+xml` badges repositories serve from their own origin: `/:o/:r/badge/gates.svg`, `/issues.svg`, `/prs.svg`, `/status.svg`, `/status/:context.svg`. Zero-IO Verdana-11 text width estimator, 64-char clamp, XML-escape, named + hex colours. Never 500s — falls back to grey "unknown" on DB/git failure. `public, max-age=60, stale-while-revalidate=300`. `src/lib/badge.ts` + `src/routes/badges.ts`. |
9ab6971Claude96
97### 2.3 Collaboration
98| Feature | Status | Notes |
99|---|---|---|
100| Issues (CRUD / comments / labels / close) | ✅ | |
101| Milestones | ✅ | `src/routes/insights.tsx` |
102| Pull requests (CRUD / review / merge) | ✅ | |
103| PR inline comments | ✅ | file+line anchored |
6fc53bdClaude104| Draft PRs | ✅ | create as draft, ready-for-review toggle, dedicated tab, merge blocked until ready |
105| Reactions (emoji) | ✅ | 8 reactions, toggle via `POST /api/reactions/:t/:id/:emoji/toggle` on issues + PRs + comments |
9ab6971Claude106| Mentions + notifications | ✅ | `src/routes/notifications.tsx` |
107| Code owners | ✅ | `src/lib/codeowners.ts` |
9a4eb42Claude108| Issue templates | ✅ | `.github/ISSUE_TEMPLATE.md` auto-prefills new issues; frontmatter stripped; `src/lib/templates.ts`. J17 extends this with `.github/ISSUE_TEMPLATE/*.md` multi-template support: scans up to 20 files, parses `name`/`about`/`title`/`labels`/`assignees` frontmatter, shows a chooser when 2+ templates exist, auto-picks a single template, falls back to legacy `ISSUE_TEMPLATE.md`, `?template=__blank` escape hatch. `src/lib/issue-templates.ts`. |
24cf2caClaude109| PR templates | ✅ | `.github/PULL_REQUEST_TEMPLATE.md` auto-prefills new PRs; `src/lib/templates.ts` |
110| Saved replies | ✅ | per-user canned comments, unique-shortcut, `/settings/replies`, `/api/user/replies` |
1e162a8Claude111| Discussions / forums | ✅ | E2 — categorised threads, pinned/locked, q-and-a answers. `src/routes/discussions.tsx` + `drizzle/0013_discussions.sql` |
112| Wikis | ✅ | E3 — markdown pages per repo with revision history + revert. DB-backed v1. `src/routes/wikis.tsx` + `drizzle/0016_wikis.sql` |
113| Projects / kanban | ✅ | E1 — per-repo boards with auto-seeded To Do/In Progress/Done columns. Notes or linked issues/PRs. `src/routes/projects.tsx` + `drizzle/0015_projects.sql` |
114| AI incident responder | ✅ | D4 — auto-issues on deploy fail, `src/lib/ai-incident.ts` |
115| AI-generated test stubs | ✅ | D8 — `src/lib/ai-tests.ts`, `/:owner/:repo/ai/tests` |
9ab6971Claude116
117### 2.4 Automation + AI
118| Feature | Status | Notes |
119|---|---|---|
ad6d4adClaude120| Webhooks (outbound, HMAC signed) | ✅ | `src/routes/webhooks.tsx` |
121| GateTest inbound callback | ✅ | `POST /api/hooks/gatetest`, bearer or HMAC |
122| Backup PAT-auth gate ingest | ✅ | `POST /api/v1/gate-runs` |
9ab6971Claude123| Gate runs (test / secret / AI review) | ✅ | `gate_runs` table, `src/routes/gates.tsx` |
124| Branch protection | ✅ | `branch_protection` table + UI |
125| Auto-repair engine | ✅ | `src/lib/auto-repair.ts` |
126| Secret scanner | ✅ | 15 patterns, `src/lib/security-scan.ts` |
127| AI security review | ✅ | Sonnet 4, `src/lib/security-scan.ts` |
128| AI commit messages | ✅ | `src/lib/ai-generators.ts` |
129| AI PR summaries | ✅ | |
3cbe3d6Claude130| AI changelogs | ✅ | auto on release create; arbitrary-range viewer at `/:owner/:repo/ai/changelog?from=&to=` (D7) |
9ab6971Claude131| AI code review | ✅ | `src/lib/ai-review.ts` |
132| AI merge conflict resolver | ✅ | `src/lib/merge-resolver.ts` |
133| AI chat (global + repo) | ✅ | `src/routes/ask.tsx` |
3cbe3d6Claude134| AI explain-this-codebase | ✅ | D6 — per-commit cached markdown, `GET /:owner/:repo/explain`, `src/lib/ai-explain.ts` + `src/routes/ai-explain.tsx` |
135| AI PR triage | ✅ | D3 — Claude Haiku suggests labels/reviewers/priority as an AI comment on PR create; `triagePullRequest` in `src/lib/ai-generators.ts`, wired in `src/routes/pulls.tsx` |
3247f79Claude136| CODEOWNERS auto-assign reviewers | ✅ | J11 — on PR open, `git diff --numstat base...head` → CODEOWNERS rule match → user IDs → `pr_review_requests` rows + `review_requested` notifications. PR detail page renders a Reviewers panel with state pills (pending/approved/changes_requested/dismissed), manual `@username` add, and dismiss. `src/lib/review-requests.ts` + `drizzle/0034_pr_review_requests.sql`. |
f9e7c01Claude137| Community profile (health standards) | ✅ | J12 — `GET /:owner/:repo/community` scores the repo on 8 items (description, README, LICENSE — required; CODE_OF_CONDUCT, CONTRIBUTING, issue templates, PR template, topics — recommended). Pure `checklistFromInputs` + `buildReport`, git-layer `computeHealth`. One-click "Add <path>" links route to the web editor. `src/lib/community.ts` + `src/routes/community.tsx`. |
8ec29ffClaude138| Pinned repositories on profile | ✅ | J13 — users pin up to 6 repos ordered explicitly; `drizzle/0035_pinned_repos.sql` adds `pinned_repositories`. Manage at `/settings/pins`. Profile page renders "Pinned" grid above the repo list with viewer-aware private filtering. `src/lib/pinned-repos.ts` + `src/routes/pinned-repos.tsx`. |
bed6b57Claude139| Issue dependencies (blocked-by / blocks) | ✅ | J14 — `drizzle/0036_issue_dependencies.sql` adds `issue_dependencies` (CHECK no-self, unique on pair, both-side indexes). Pure `wouldCreateCycle` BFS + `summariseBlockers`. `addDependency` enforces same-repo, no-self, no-dup, no-cycle with `{ok, reason}` error taxonomy. Issue detail page gets a "Dependencies" panel with "Blocked by" / "Blocks" lists, state pills, `#number` add form + per-row dismiss. `src/lib/issue-dependencies.ts` + routes in `src/routes/issues.tsx`. |
a53ceabClaude140| Deterministic release-notes generator | ✅ | J15 — `src/lib/release-notes.ts` classifies commits by conventional-commit prefix (feat/fix/perf/refactor/docs/chore/revert/style/build/ci/test + aliases + `!` breaking marker + trailing `(#N)` capture) into 13 ordered buckets and renders Markdown with a Breaking-changes section, per-bucket headings, Contributors list, and Full-Changelog compare link. "Generate from commits" button on the new-release form prefills the notes textarea without losing other field state; AI-disabled repos now fall through to the deterministic path instead of publishing blank notes. `src/routes/releases.tsx` adds `POST /:owner/:repo/releases/generate-notes`. |
21ff9beClaude141| PR auto-merge when checks pass | ✅ | J16 — `drizzle/0037_pr_auto_merge.sql` adds `pr_auto_merge` (unique on `pull_request_id`, merge-method text, commit-title/message overrides, last_status + notified_ready). Pure `computeAutoMergeAction` state machine returns `wait\|merge\|skip` with reason codes (`not_enabled\|pr_closed\|pr_draft\|no_checks\|checks_pending\|checks_failed\|checks_passed`). `src/lib/pr-auto-merge.ts` exposes enable/disable + record-evaluation helpers; `src/lib/pr-auto-merge-trigger.ts` is called fire-and-forget from the commit-status POST path — it resolves each opted-in PR's head branch, matches against the incoming SHA, and posts a one-shot readiness comment + PR-author notification on first transition to ready. PR detail page shows an `AutoMergePanel` with live status colour (green/yellow/red) and merge-method selector. |
7c975c6Claude142| Repository pulse / activity summary | ✅ | J18 — `GET /:owner/:repo/pulse[?window=1d\|7d\|30d\|90d]` renders GitHub-parity Pulse: commit activity, PR opened/merged/closed/active, issue opened/closed/active, top contributors, recent merges + closes. `src/lib/repo-pulse.ts` is pure-first (`parseWindow`, `windowStart`, `summariseCommits` grouping by lowercased email with count-desc/name-asc sort, `summarisePrs`, `summariseIssues`, `buildPulseReport` one-shot). softAuth read-only; degrades to an empty-state report on DB/git failure. Linked from the Insights page header. |
26484ebClaude143| Atom / RSS feeds | ✅ | J19 — `GET /:owner/:repo/{commits,releases,issues}.atom` serve Atom 1.0 feeds (up to 50 entries each). Pure renderer in `src/lib/atom-feed.ts` — `renderAtomFeed` emits XML-declaration-prefixed `<feed>` with required `<id>/<title>/<updated>/<link rel=self>`, per-entry `<id>/<title>/<link>/<updated>/<published>/<author>/<summary>`. All five XML metacharacters escaped. `toIsoUtc` normalises Date + ISO inputs, falls back to epoch on junk. Cache headers `public, max-age=60, stale-while-revalidate=300`; `application/atom+xml; charset=utf-8` content-type. Private repos 404 (as empty feed doc) for non-owner viewers. |
d6b4e67Claude144| Stale issue detector | ✅ | J20 — `GET /:owner/:repo/issues/stale[?period=30d\|60d\|90d\|180d]` lists every open issue whose `updated_at` is older than the selected threshold, sorted oldest-first with per-issue `daysSinceUpdate` + comment count + age-colour (>=90d amber, >=180d red). Four age-bucket cards show the distribution (`30-60`, `60-90`, `90-180`, `180+`). Pure `filterStale` / `bucketByStaleness` / `buildStaleReport` in `src/lib/stale-issues.ts` accept both `Date` and ISO inputs and gracefully drop unparseable timestamps. softAuth read-only; private repos 404 for non-owner viewers. Linked from the Issues toolbar via a "Stale" button. Non-destructive — we only surface staleness, never auto-close. |
f3e1844Claude145| CODEOWNERS validator | ✅ | J21 — `GET /:owner/:repo/codeowners` lints the CODEOWNERS file (standard locations: root / `.github/` / `docs/`) on the default branch against known users + teams and surface-level syntax. Pure `lexCodeowners` (line-aware, CRLF tolerant, inline-comment strip, detects pattern-with-no-owners), `classifyOwnerToken` (user / team / email / invalid), `isPlausiblePattern` (bracket balance, whitespace), `validateCodeowners` (async, takes `OwnerResolver`) in `src/lib/codeowners-lint.ts`. Findings are anchored to 1-indexed line numbers with an `error\|warning\|info` severity + stable `code` taxonomy (`empty_file\|no_owners\|empty_pattern\|bad_pattern_syntax\|bad_owner_format\|unknown_user\|unknown_team\|duplicate_pattern\|duplicate_owner\|missing_catchall`). UI shows four summary cards (rules / errors / warnings / infos), a findings list, and the file body with line numbers. Resolver memoises per-request. Non-destructive — report only. |
7c0203eClaude146| Code review suggestion blocks | ✅ | J22 — `` ```suggestion `` fenced blocks inside PR comments are detected (pure `extractSuggestions` in `src/lib/code-suggestions.ts`) and rendered on the PR page with a "Commit suggestion" button when the viewer is the PR author, comment author, or repo owner. `POST /:owner/:repo/pulls/:number/comments/:commentId/apply-suggestion` reads the file on the PR's head branch, runs pure `applySuggestionToContent` (preserves CRLF/LF line endings + trailing-newline presence, validates line range + detects no-op), and commits via git plumbing (`hash-object` → `ls-tree -r` rewrite → `mktree` → `commit-tree` → `update-ref`) with a `Co-authored-by:` trailer crediting the applier. `requireAuth`; private repos 404 for non-owners; closed PRs redirect silently. |
831a117Claude147| Issue/PR search query DSL | ✅ | J23 — GitHub-style search DSL on the issue list. `GET /:owner/:repo/issues?q=…` accepts `is:open\|closed`, `author:<user>`, `label:<name>` (repeatable, AND), `-label:<name>` (exclude), `no:label`, `milestone:"<title>"`, `sort:created-desc\|created-asc\|updated-desc\|updated-asc\|comments-desc`, and any other text → case-insensitive substring match against title+body. Pure `tokenise` / `parseIssueQuery` / `matchIssue` / `sortIssues` / `applyQuery` / `formatIssueQuery` in `src/lib/issue-query.ts` — never throws; unknown qualifiers fall back to text; `sort:` values not in the allow-list fall back to default. The issues route joins labels, applies the DSL in-memory, and renders an inline search bar + collapsible syntax cheat-sheet + live match count. Tab pill counts remain over all issues so filters don't collapse them. |
d6daf49Claude148| Branch rename with cascades | ✅ | J24 — `GET/POST /:owner/:repo/settings/branches` (owner-only). Pure `validateBranchName` / `planRename` / `shouldRewriteProtectionPattern` in `src/lib/branch-rename.ts` enforce `git check-ref-format(1)` (rejects `..`, `@{`, bare `@`, leading `-`, leading/trailing `.` or `/`, `//`, `.lock` suffix, whitespace, `~^:?*[\`, control chars). Git plumbing `renameBranch` (creates `refs/heads/<to>` → deletes `refs/heads/<from>` with rollback on partial failure) + `setHeadBranch` live in `src/git/repository.ts`. After the ref move the route cascades to `repositories.default_branch`, `pull_requests.base_branch` / `head_branch`, `merge_queue_entries.base_branch`, and exact-match `branch_protection.pattern` rows (globs untouched), with every rename audited as `branch.rename`. History is preserved — only the ref name changes. |
9090df3Claude149| Time-to-first-response metric | ✅ | J25 — `GET /:owner/:repo/insights/response-time[?window=7\|30\|90\|365\|0]` renders p50/mean/p90/fastest/slowest response times, a four-bucket distribution (≤1h, 1h–1d, 1d–1w, >1w), and the oldest-first list of open issues still waiting for a reply. Pure `computeTimeToFirstResponse` (ignores comments by the issue author, clamps negative deltas, skips unparseable dates), `computeIssueStats` (window filter), `summariseResponseTimes` (inclusive-method percentiles), `bucketResponseTimes`, `formatDuration` (`ms`/`s`/`m`/`h m`/`d h`), `buildResponseReport` one-shot, `parseWindow` (allow-list `VALID_WINDOWS`) in `src/lib/response-time.ts`. Linked from the Insights page header. softAuth; private repos 404 for non-owner viewers; DB failure degrades to empty report — never 500. |
611431dClaude150| Audit log CSV export | ✅ | J26 — `GET /settings/audit.csv` + `GET /:owner/:repo/settings/audit.csv` stream the audit log as RFC 4180 CSV with `Content-Disposition: attachment`. Pure `csvCell` / `csvRow` / `csvDocument` / `formatAuditCsv` / `auditCsvFilename` helpers in `src/lib/audit-csv.ts` implement CRLF termination, `"…"` wrapping + `""` escaping when cells contain `,"\n\r`, and a CSV-injection guard that prefixes any cell starting with `=+-@\t\r` with a single-quote so spreadsheet formula engines don't evaluate attacker-supplied content. Header row is `id,when,actor,action,targetType,targetId,ip,userAgent,metadata`. Same visibility rules as the HTML pages (requireAuth + owner-only for repo variant). Filename is `audit-<scope>-<ISO>.csv`. `Cache-Control: private, no-store`. |
c02a55bClaude151| Branch staleness / age report | ✅ | J27 — `GET /:owner/:repo/branches/age[?threshold=0\|30\|60\|90\|180][&sort=age-desc\|age-asc\|name\|ahead-desc\|behind-desc]` walks every branch, fetches the tip commit + ahead/behind counts vs the default branch via `aheadBehind(base, head)` (new `git rev-list --left-right --count` helper in `src/git/repository.ts`), and renders KPI cards (total / non-default / merged / unmerged / median age / oldest), a four-bucket distribution (Fresh <30d, Aging 30–59d, Stale 60–89d, Abandoned ≥90d or missing tip), and a branch table with ahead/behind/last-commit/status columns. Pure `parseThreshold` / `parseSort` / `computeDaysOld` / `classifyBranchAge` / `computeBranchRow` (merged = !default && ahead=0) / `bucketBranches` / `filterByThreshold` / `summariseBranches` (avg + median, excludes default) / `sortBranchRows` (non-mutating, null daysOld sinks) / `buildBranchReport` / `categoryLabel` / `thresholdLabel` / `sortLabel` in `src/lib/branch-age.ts`. softAuth; private repos 404 for non-owner viewers; git failures degrade to empty report — never 500. Linked from repo settings. |
b184a75Claude152| Issue duplicate suggestions | ✅ | J28 — `GET /:owner/:repo/issues/similar.json?q=<title>[&limit][&state=open\|closed]` returns ranked matches as JSON for new-issue-form inline suggestions. `GET /:owner/:repo/issues/:n/similar` is a standalone HTML page ranking related issues against the target title. Pure token-Jaccard ranker in `src/lib/issue-similarity.ts`: `tokeniseTitle` lowercases + strips non-`\p{L}\p{N}_-` + drops stopwords + drops tokens <2 chars; `jaccard` is Unicode-safe `|A∩B| / |A∪B|`; `rankCandidates` sorts score-desc with createdAt-desc + number-desc tie-breaks, honours `minScore` (default 0.15) / `limit` (default 5) / `excludeId` / `excludeNumber` / `state`. `formatSimilarityPercent` clamps to [0,1] and emits `"47%"`. Candidates capped at last 500 issues per repo. softAuth; private repos 404 for non-owner viewers. |
8c5346cClaude153| PR lead-time metric | ✅ | J29 — `GET /:owner/:repo/insights/lead-time[?window=7\|30\|90\|365\|0]` renders p50/mean/p90/fastest/slowest PR lead times (created→merged), a four-bucket merge-time distribution, and the oldest still-open non-draft PRs. Pure `computeLeadTime` / `computePrStats` (anchors the window on `mergedAt` for merged PRs, `createdAt` otherwise, so a PR merged yesterday with origin 60 days ago still lands in a 30-day window) / `summariseLeadTimes` (inclusive-method percentiles; separate counters for merged / openNonDraft / openDraft / closedUnmerged) / `bucketLeadTimes` / `buildLeadTimeReport` in `src/lib/pr-lead-time.ts`. Reuses `parseWindow` + `formatDuration` + `VALID_WINDOWS` from Block J25's `response-time.ts` to stay DRY. Linked from the Insights page header alongside Response time + Pulse. softAuth; private repos 404 for non-owner viewers. |
07efa08Claude154| Repository language breakdown | ✅ | J30 — `GET /:owner/:repo/languages[?vendored=1&fold=N&ref=<branch>]` renders a GitHub-parity stacked percentage bar + legend + per-language table (bytes / file count / share) against the repo's default branch (or an explicit `ref`). Pure `detectLanguage` (extension map ~80 entries + filename map for Dockerfile/Makefile/Rakefile/CMakeLists.txt/meson.build/etc., case-insensitive, dotfiles are null, last-extension wins), `isVendoredOrGenerated` (prefixes `node_modules/`, `vendor/`, `dist/`, `build/`, `.next/`, `.nuxt/`, `coverage/`, `.git/`, `target/`, `bin/` + lockfile basenames `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lockb`, `Cargo.lock`, `composer.lock`, `Gemfile.lock`, `poetry.lock`), `computeLanguageStats` (vendored opt-out, per-file `maxFileSize` cap so a 500MB blob can't bias the pie, `minLanguageBytes` fold, zero/negative sizes skipped), `foldIntoOther` (idempotent rebucketing, Other always sorts last), `formatBytes` (B/KB/MB/GB/TB with 10+ threshold for dropping decimals), `formatPercent` (clamp [0,100], NaN → "0%"), `buildLanguageReport` one-shot, plus `LANGUAGE_COLORS` GitHub-ish palette in `src/lib/language-stats.ts`. New `listTreeRecursive(owner, name, ref)` helper in `src/git/repository.ts` uses `git ls-tree -r -l -z` (null-delimited for safety), skips trees/submodules and mode `120000` symlinks so their target-length doesn't pollute sizes, cached under `${owner}/${name}:tree-recursive:${ref}`, returns `[]` on git failure. Linked from the Insights page header. softAuth; private repos 404 for non-owner viewers. |
ce08e97Claude155| Repository size audit | ✅ | J31 — `GET /:owner/:repo/insights/size[?top=N&ref=<branch>]` renders "where are the bytes?" for the default branch (or an explicit ref): five KPI cards (file count / total / largest / median / mean), a five-bucket size-class histogram (tiny <1KB / small <100KB / medium <1MB / large <10MB / xlarge ≥10MB), a top-level directory breakdown with percentage shares, and the largest N files (default 25, capped at 200) linked through to the blob viewer. Pure `topLevelDir`, `classifyFileSize`, `summariseSize` (total/mean/median/largest/smallest over valid entries, drops NaN/negative/non-string-path input), `bucketBySize`, `topLargestFiles` (non-mutating, stable alphabetical tie-break, optional `minBytes` floor), `summariseByTopDir` (groups by first path segment, `.` is the root bucket sorting last on ties), `buildSizeReport` one-shot in `src/lib/repo-size.ts`. Reuses `listTreeRecursive` from J30 + `formatBytes`/`formatPercent` from `language-stats.ts` so the route stays a thin shell. Linked from the Insights page header. softAuth; private repos 404 for non-owner viewers; git failures degrade to an empty-state report — never 500. |
28bc555Claude156| PR size distribution metric | ✅ | J32 — `GET /:owner/:repo/insights/pr-size[?window=7\|30\|90\|365\|0&top=N]` scores every PR in the window against the classic five-tier size taxonomy (XS ≤10 / S ≤50 / M ≤250 / L ≤1000 / XL >1000 lines changed). Renders eight KPI cards (total / merged / open / median / mean / p90 / largest / small-PR ratio), a five-class histogram with traffic-light borders (green→red), and the largest N PRs with file count + `+a/-d` breakdown + a colour-coded size-class badge. New `diffNumstat(owner, name, base, head)` helper in `src/git/repository.ts` runs `git diff --numstat base..head` and sums additions/deletions/files (binaries count as 0). Pure rollup in `src/lib/pr-size.ts`: `PR_SIZE_CLASSES` (inclusive-below boundaries, Infinity tail), `classifyPrSize` (NaN/negative → xs), `computePrSizeStats` (anchors window on `mergedAt` for merged PRs so recent merges with old `createdAt` land in recent windows; drops unparseable dates; clamps negative/NaN additions and deletions to 0), `summarisePrSizes` (inclusive-method p50/p90, merged-count, non-draft-open-count, small-PR ratio rounded to 1 decimal), `bucketPrSizes` (always returns all five buckets so UI can render zero-count cells), `topLargestPrs` (non-mutating; PR-number-desc tie-break; defaults bogus limits to 10), `buildPrSizeReport` one-shot. Reuses `parseWindow` / `VALID_WINDOWS` / `DEFAULT_WINDOW_DAYS` from J25's response-time.ts. Per-PR numstat errors degrade to 0 lines (PR still renders in XS bucket) rather than crashing the page. PR fetch capped at 500; topN capped at 50. Linked from the Insights page header. softAuth; private repos 404 for non-owner viewers. |
5e888b7Claude157| GitHub Actions equivalent (workflow runner) | ✅ | `src/lib/workflow-parser.ts`, `src/lib/workflow-runner.ts`, `src/routes/workflows.tsx`; `.gluecron/workflows/*.yml` auto-discovered on push; Bun subprocess executor, per-step timeouts, size-capped logs |
3cbe3d6Claude158| Dependabot equivalent (AI dep bumper) | ✅ | D2 — `dep_update_runs` table, npm registry fetch, plan + apply bumps, creates `gluecron/dep-update-*` branch + PR row via git plumbing. `src/lib/dep-updater.ts`, `src/routes/dep-updater.tsx`, settings UI at `/:owner/:repo/settings/dep-updater`. |
08420cdClaude159| Code scanning UI | ✅ | I5 — `src/routes/code-scanning.tsx`, `GET /:owner/:repo/security`. Aggregates last-100 `gate_runs` matching `%scan%`/`%security%`, rolls up latest status per gate, shows failed/repaired/total cards + scanner status list + recent runs. |
3cbe3d6Claude160| Copilot code completion | ✅ | D9 — `POST /api/copilot/completions` (PAT/OAuth/session), `GET /api/copilot/ping`. `src/lib/ai-completion.ts`, `src/routes/copilot.ts`. LRU-cached, rate-limited 60/min. |
161| Semantic code search | ✅ | D1 — see 2.2 |
9ab6971Claude162
163### 2.5 Platform
164| Feature | Status | Notes |
165|---|---|---|
166| Dashboard | ✅ | `src/routes/dashboard.tsx` |
167| Explore / discover | ✅ | |
168| Global search | ✅ | repos / users / issues / PRs |
169| Insights (graph, contributors, green rate) | ✅ | `src/routes/insights.tsx` |
170| Releases + tags | ✅ | AI changelog |
171| Personal access tokens | ✅ | SHA-256 hashed |
058d752Claude172| OAuth app provider | ✅ | `src/routes/oauth.tsx`, `src/routes/developer-apps.tsx`, `src/lib/oauth.ts`; `oauth_apps` + `oauth_authorizations` + `oauth_access_tokens` tables |
06139e6Claude173| GitHub Apps equivalent | ✅ | H2 — `src/lib/marketplace.ts` `generateBearerToken`/`verifyInstallToken` (1h TTL, `ghi_` prefix, sha256 hashed). Each app gets a `<slug>[bot]` identity (`app_bots`). Permissions enforced via `hasPermission` (write implies read). |
71cd5ecClaude174| GraphQL API | ✅ | G2 — see 2.6 |
058d752Claude175| Organizations + teams | ✅ | B1+B2+B3 shipped: `src/routes/orgs.tsx`, `src/lib/orgs.ts`; org-owned repos (`repositories.orgId`); team-based CODEOWNERS (`@org/team` resolution) |
edf7c36Claude176| Enterprise SAML / SSO | ✅ | I10 — OIDC (Okta / Azure AD / Auth0 / Google Workspace). `src/lib/sso.ts` + `src/routes/sso.tsx`, `drizzle/0027_sso_oidc.sql` (tables `sso_config` singleton + `sso_user_links`). Admin config at `/admin/sso`; `/login/sso` starts auth-code flow with state+nonce cookies; `/login/sso/callback` exchanges code, fetches userinfo, links by `sub` (or by email, or auto-creates). Optional email-domain allow-list + auto-create toggle. |
058d752Claude177| 2FA / TOTP | ✅ | `src/routes/settings-2fa.tsx`, `src/lib/totp.ts`; `user_totp` + `user_recovery_codes` tables |
178| Passkeys / WebAuthn | ✅ | `src/routes/passkeys.tsx`, `src/lib/webauthn.ts`; `user_passkeys` + `webauthn_challenges` tables |
25a91a6Claude179| Packages registry (npm / docker / etc) | ✅ | `src/lib/packages.ts`, `src/routes/packages-api.ts`, `src/routes/packages.tsx`; npm protocol (packument, tarball, publish, yank); PAT (`glc_`) auth via Authorization header; container registry deferred |
180| Pages / static hosting | ✅ | `src/lib/pages.ts`, `src/routes/pages.tsx`; serves blobs from bare git at latest `gh-pages` commit; per-repo settings (source branch/dir, custom domain); short-cache headers |
1e162a8Claude181| Gists | ✅ | E4 — multi-file tiny repos with per-revision JSON snapshots + stars. `src/routes/gists.tsx` + `drizzle/0014_gists.sql` |
08420cdClaude182| Sponsors | ✅ | I6 — `src/routes/sponsors.tsx`, `drizzle/0023_sponsors.sql` (tables `sponsorship_tiers`, `sponsorships`). Public `/sponsors/:user` page with tier cards + recent public sponsors; maintainer view at `/settings/sponsors` with add/retire tiers. Payment rails deferred — captures intent + thank-you notes. |
06139e6Claude183| Marketplace | ✅ | H1 — `src/routes/marketplace.tsx` + `src/lib/marketplace.ts`, `drizzle/0021_marketplace_and_apps.sql` (5 tables: `apps`, `app_installations`, `app_bots`, `app_install_tokens`, `app_events`). Public `/marketplace` directory, `/marketplace/:slug` detail + install, `/settings/apps` personal installs, `/developer/apps-new` registration, `/developer/apps/:slug/manage` event log + token issuance. |
25a91a6Claude184| Environments / deployment tracking | ✅ | `src/routes/deployments.tsx` — grouped by env, success-rate rollup, per-deploy detail. Protected environments (`src/routes/environments.tsx`, `src/lib/environments.ts`) with reviewer-gated approval, branch-glob restrictions, approve/reject decisions recorded in `deployment_approvals` |
a79a9edClaude185| Merge queues | ✅ | E5 — serialised merge with re-test. `src/lib/merge-queue.ts`, `src/routes/merge-queue.tsx`, `drizzle/0017_merge_queue.sql`; per `(repo, base_branch)` queue, owner-only process-next re-runs gates against latest base before merging. |
186| Required checks matrix | ✅ | E6 — per branch-protection named check list. `src/routes/required-checks.tsx`, `drizzle/0018_required_checks.sql`; `listRequiredChecks` + `passingCheckNames` helpers in `src/lib/branch-protection.ts`; merge handler verifies every required name has a passing gate_run or workflow_run. |
187| Protected tags | ✅ | E7 — owners can mark tag patterns (`v*`, `release-*`) protected. `src/lib/protected-tags.ts`, `src/routes/protected-tags.tsx`, `drizzle/0019_protected_tags.sql`; advisory enforcement via post-receive audit log (v1). |
9ab6971Claude188
189### 2.6 Observability + safety
190| Feature | Status | Notes |
191|---|---|---|
192| Rate limiting | ✅ | `src/middleware/rate-limit.ts` |
193| Request-ID tracing | ✅ | `src/middleware/request-context.ts` |
194| Health / readiness / metrics | ✅ | `/healthz` `/readyz` `/metrics` |
195| Audit log (table) | ✅ | `audit_log` table |
6fc53bdClaude196| Audit log UI | ✅ | `/settings/audit` (personal) + `/:owner/:repo/settings/audit` (per-repo, owner-only) |
8f50ed0Claude197| Traffic analytics per repo | ✅ | F1 — `src/lib/traffic.ts` + `src/routes/traffic.tsx`, `drizzle/0020_analytics_and_admin.sql`; owner-only 7/14/30/90d windows, ascii-bar daily chart. SHA-256-truncated IP for unique visitors. Fire-and-forget wiring in `web.tsx` + `git.ts`. |
198| Org insights dashboard | ✅ | F2 — `src/routes/org-insights.tsx`; `computeOrgInsights(orgId)` rollup of gate green-rate + PR/issue counts + per-repo breakdown. `GET /orgs/:slug/insights`. |
199| Site admin panel | ✅ | F3 — `src/lib/admin.ts` + `src/routes/admin.tsx`, tables `site_admins` + `system_flags`. Bootstrap rule (oldest user wins until `site_admins` populated). Flags: registration_locked, site_banner_*, read_only_mode. |
200| Billing + quotas | ✅ | F4 — `src/lib/billing.ts` + `src/routes/billing.tsx`, tables `billing_plans` + `user_quotas` seeded free/pro/team/enterprise. `/settings/billing` personal view + `/admin/billing` site-admin override. |
24cf2caClaude201| Email notifications | ✅ | opt-in per kind (mention/assign/gate-fail) via `/settings`; provider-pluggable `src/lib/email.ts` (log default, resend in prod) |
08420cdClaude202| Email digest | ✅ | I7 — `src/lib/email-digest.ts` + `drizzle/0024_email_digest.sql` (`users.notify_email_digest_weekly` + `last_digest_sent_at`). `composeDigest` pulls notifications + failed/repaired gates + merged PRs over last 7d, renders text + escaped HTML. `/settings/digest/preview` for self-preview; `/admin/digests` dashboard + `POST /admin/digests/run` fires `sendDigestsToAll`; `POST /admin/digests/preview` sends to one user. Never throws. |
eae38d1Claude203| Mobile PWA | ✅ | G1 — `src/routes/pwa.ts` serves `/manifest.webmanifest` + `/sw.js` + `/icon.svg`; Layout injects manifest link + SW registration. Offline-capable (network-first for HTML). |
204| GraphQL API | ✅ | G2 — `src/lib/graphql.ts` parser + executor, `src/routes/graphql.ts` endpoint at `POST /api/graphql`, GraphiQL-lite explorer at `GET /api/graphql`. Queries only (viewer/user/repository/search/rateLimit). |
205| Official CLI | ✅ | G3 — `cli/gluecron.ts` Bun-compilable single binary. REST + GraphQL client, `~/.gluecron/config.json` 0600. |
206| VS Code extension | ✅ | G4 — `vscode-extension/` with commands for explain / open-on-web / semantic search / generate tests. |
9ab6971Claude207| Native mobile apps | ❌ | |
6fc53bdClaude208| Dark mode | ✅ | default |
209| Light-mode toggle | ✅ | `/theme/toggle` + `theme` cookie, pre-paint script avoids FOUC, nav sun/moon icon |
9ab6971Claude210| Keyboard shortcuts | ✅ | `/shortcuts` page |
71cd5ecClaude211| Command palette | ✅ | I4 — `src/views/layout.tsx` injects a Cmd+K palette with ~20 canonical destinations, arrow-key navigation + fuzzy match. Backdrop click or Esc closes. |
9ab6971Claude212
213---
214
215## 3. BUILD PLAN (BLOCKS)
216
217Each block is a self-contained unit. Order matters for dependencies. Each block ends with tests + commit + push.
218
219### BLOCK A — Hardening the current surface
220Polish what's shipped before adding more. **Priority: do this first if parity gaps are minor.**
6fc53bdClaude221- **A1** — Dark/light theme toggle (cookie, CSS variable swap) ✅
222- **A2** — Audit log UI page (`/settings/audit` + `/:owner/:repo/settings/audit`) ✅
223- **A3** — Reactions UI on issues / PRs / comments (data exists) ✅
224- **A4** — Draft PR toggle + filter ✅
24cf2caClaude225- **A5** — Issue + PR templates (`.github/*_TEMPLATE.md` auto-prefill) ✅
226- **A6** — Saved replies per user ✅
227- **A7** — Environments + deployment history UI (`deployments` table) ✅
228- **A8** — Email notifications (opt-in, provider-pluggable) ✅
229
230**BLOCK A COMPLETE.** Next: BLOCK B (Identity + orgs).
9ab6971Claude231
232### BLOCK B — Identity + orgs
058d752Claude233- **B1** — Organizations (schema: `organizations`, `org_members`, `teams`, `team_members`) → ✅ shipped (`6563f0a`)
6563f0aClaude234 - Helpers in `src/lib/orgs.ts`: slug validation, role rank, reserved-slug set, loaders
235 - Routes in `src/routes/orgs.tsx`: list / create / profile / people / teams / team detail
236 - Role-based guards: admin adds members, owner grants owner, last-owner demote/remove blocked
237 - All sensitive actions `audit()`'d (org.create, member.add/role/remove, team.create, team.member.add/remove)
058d752Claude238- **B2** — Repos owned by orgs (nullable `repositories.orgId`) → ✅ shipped (`7437605`)
239- **B3** — Team-based CODEOWNERS (`@org/team` resolution) → ✅ shipped (`40d3e3f`)
240- **B4** — 2FA / TOTP (enroll, recovery codes) → ✅ shipped (`7298a17`)
241- **B5** — WebAuthn / passkeys → ✅ shipped (`2df1f8c`)
242- **B6** — OAuth 2.0 provider (third-party apps can request access) → ✅ shipped (pending final commit)
9ab6971Claude243
244### BLOCK C — Runtime + hosting
5e888b7Claude245- **C1** — Actions-equivalent workflow runner → ✅ shipped (`eafe8c6`)
246 - Workflow YAML parser (`src/lib/workflow-parser.ts`) — hand-rolled subset
247 - Background worker (`src/lib/workflow-runner.ts`) — Bun.spawn, size-capped logs, SIGTERM→SIGKILL timeouts
248 - Auto-discovery from `.gluecron/workflows/*.yml` on default-branch push
249 - UI at `/:owner/:repo/actions` with manual trigger + cancel
25a91a6Claude250- **C2** — Package registry (npm protocol) → ✅ shipped
251 - Packument + tarball + publish + yank via `PUT /npm/<name>` + `GET /npm/<name>`
252 - PAT (`glc_`) bearer auth for CLI clients; add `//host/npm/:_authToken=<PAT>` to .npmrc
253 - Container registry deferred (schema ready for it)
254- **C3** — Pages / static hosting → ✅ shipped
255 - Serves `/:owner/:repo/pages/*` from the latest successful `pages_deployments` row
256 - Auto-records on push to the repo's configured source branch (default `gh-pages`)
257 - Settings UI at `/:owner/:repo/settings/pages` + manual redeploy
258- **C4** — Environments with protected approvals → ✅ shipped
259 - Per-repo `environments` with reviewer list + branch-glob allowlist
260 - Auto-deploy on main is gated by `requiresApprovalFor()`; pending rows show status `pending_approval`
261 - Approve/reject at `POST /:owner/:repo/deployments/:id/approve|reject`
9ab6971Claude262
263### BLOCK D — AI-native differentiation
264This is where GlueCron beats GitHub outright. **Priority: ship these loud.**
3cbe3d6Claude265- **D1** — Semantic code search → ✅ shipped. `src/lib/semantic-search.ts` + `src/routes/semantic-search.tsx`. `code_chunks` table stores chunk embeddings as JSON (upgrade path to `pgvector`). Embedding provider: Voyage AI `voyage-code-3` when `VOYAGE_API_KEY` is set, otherwise deterministic 512-dim hashing fallback. Index via `POST /:owner/:repo/search/semantic/reindex` (owner-only).
266- **D2** — AI dependency updater → ✅ shipped. `src/lib/dep-updater.ts` + `src/routes/dep-updater.tsx`. `dep_update_runs` table tracks run history. Parses `package.json`, queries `registry.npmjs.org`, plans bumps (skips workspace/github specs + downgrades), writes an `gluecron/dep-update-<ts>` branch via git plumbing (`hash-object` + `mktree` + `commit-tree` + `update-ref`), inserts a pull_requests row with a markdown bump table. Settings UI at `/:owner/:repo/settings/dep-updater` with "Run now".
267- **D3** — AI PR triage → ✅ shipped. `triagePullRequest` in `src/lib/ai-generators.ts`; hooked into PR create in `src/routes/pulls.tsx` (fire-and-forget). Posts a non-applied "## AI Triage" comment with suggested labels, reviewers, priority, and risk area. Suggestions only — PR author stays in control.
1e162a8Claude268- **D4** — AI incident responder → ✅ shipped. `src/lib/ai-incident.ts` exports `onDeployFailure(args)` — on deploy-fail hooks, samples ~10 recent commits, calls Sonnet 4 for a structured root-cause JSON, opens an issue (number via `serial`), best-effort attaches `incident` label, sets `deployments.blockedReason="auto-issue #N"`. Wired from `src/hooks/post-receive.ts triggerCrontechDeploy` (fire-and-forget) and from `POST /:owner/:repo/deployments/:id/retry-incident` (owner-only re-run button on the deployment detail page). Never throws; degrades to deterministic body when no `ANTHROPIC_API_KEY`.
269- **D5** — AI code reviewer blocks merges → ✅ shipped. `src/lib/branch-protection.ts` exports `matchProtection(repoId, branch)` (exact > glob, reuses `matchGlob` from environments.ts), `evaluateProtection(rule, ctx)` pure decision helper (checks `requireAiApproval` / `requireGreenGates` / `requireHumanReview` / `requiredApprovals`), and `countHumanApprovals(prId)` (LGTM/`+1`/approved heuristic on non-AI PR comments). Wired into `src/routes/pulls.tsx` merge handler after the existing hard-gate filter — blocks merge with readable reasons when rule fails. 8 unit tests in `src/__tests__/branch-protection.test.ts`.
3cbe3d6Claude270- **D6** — AI "explain this codebase" → ✅ shipped. `src/lib/ai-explain.ts` + `src/routes/ai-explain.tsx`. Samples up to ~25 representative files (~60KB cap), generates a Markdown explanation via Sonnet 4, caches per (repo, commit sha) in `codebase_explanations`. `GET /:owner/:repo/explain` + owner-only `POST /:owner/:repo/explain/regenerate`. Explain link added to `RepoNav`.
271- **D7** — AI changelog for every commit range → ✅ shipped. `src/routes/ai-changelog.tsx`. `GET /:owner/:repo/ai/changelog?from=&to=(&format=markdown)` — runs `git log` on the range, calls existing `generateChangelog`, renders form + rendered Markdown + copy-box; `format=markdown` returns `text/markdown` for CLI/CI consumers. Caps at 500 commits.
1e162a8Claude272- **D8** — AI-generated test suite → ✅ shipped. `src/lib/ai-tests.ts` exports `detectLanguage(path)`, `detectTestFramework(repo tree)`, `buildTestsPrompt(...)`, `suggestedTestPath(...)`, `generateTestStub({path, content, framework, language})` (returns `{code:"", framework:"fallback"}` when AI unavailable), `contentTypeFor(path)`. Route `src/routes/ai-tests.tsx` adds `GET /:owner/:repo/ai/tests` (form + file picker), `GET /:owner/:repo/ai/tests?format=raw` (raw text with correct MIME), `POST /:owner/:repo/ai/tests/generate` (requireAuth, renders highlighted source + generated failing test, copy button). Stubs are intentionally failing so the author fills them in.
3cbe3d6Claude273- **D9** — Copilot-style completion endpoint → ✅ shipped. `src/lib/ai-completion.ts` + `src/routes/copilot.ts`. `POST /api/copilot/completions` (requireAuth accepts PAT/OAuth/session), `GET /api/copilot/ping`. Claude Haiku; in-memory LRU (size 200, 5-min TTL); code-fence stripping; 60/min rate limit per caller.
9ab6971Claude274
275### BLOCK E — Collaboration parity
1e162a8Claude276- **E1** — Projects / kanban boards → ✅ shipped. `src/routes/projects.tsx`, tables `projects`/`project_columns`/`project_items` (migration 0015). Create creates three default columns (To Do/In Progress/Done); cards carry note or issue/pr link; one-click move between columns; owner-only close.
277- **E2** — Discussions (forum threads per repo) → ✅ shipped. `src/routes/discussions.tsx`, tables `discussions`/`discussion_comments` (migration 0013). Categorised (general/q-and-a/ideas/announcements/show-and-tell), pinnable, lockable, q-and-a answers.
278- **E3** — Wikis → ✅ shipped as DB-backed v1. `src/routes/wikis.tsx`, tables `wiki_pages`/`wiki_revisions` (migration 0016). Slug auto-derived; every edit bumps revision + appends a revision row; owner can revert. Git-backed mirror deferred.
279- **E4** — Gists → ✅ shipped. `src/routes/gists.tsx`, tables `gists`/`gist_files`/`gist_revisions`/`gist_stars` (migration 0014). Multi-file; each edit takes a JSON snapshot into `gist_revisions` keyed on revision number; stars toggle; secret gists hidden from non-owners.
a79a9edClaude280- **E5** — Merge queues → ✅ shipped. `src/lib/merge-queue.ts`, `src/routes/merge-queue.tsx`, table `merge_queue_entries` (migration 0017). Per `(repo, base_branch)` FIFO queue; `POST /:owner/:repo/pulls/:n/enqueue` adds from the PR page; owner-only `POST /queue/process-next` re-runs gates against latest base before merging the head. Entries have queued | running | merged | failed | dequeued states.
281- **E6** — Required status checks matrix → ✅ shipped. `src/routes/required-checks.tsx`, table `branch_required_checks` (migration 0018); helpers `listRequiredChecks` + `passingCheckNames` in `src/lib/branch-protection.ts`. Settings UI at `/:owner/:repo/gates/protection/:id/checks`; merge handler (`src/routes/pulls.tsx`) loads required names + computes passing set from `gate_runs` (passed/repaired) + `workflow_runs` (success) and blocks if any required name is missing.
282- **E7** — Protected tags → ✅ shipped. `src/lib/protected-tags.ts`, `src/routes/protected-tags.tsx`, table `protected_tags` (migration 0019). Settings CRUD at `/:owner/:repo/settings/protected-tags`; patterns use same glob syntax as branch protection. v1 enforcement is advisory: post-receive logs audit entries (`protected_tags.{create|update|delete}_violation_candidate`) so owners can see violations; pre-receive blocking is future work.
9ab6971Claude283
284### BLOCK F — Observability + admin
8f50ed0Claude285- **F1** — Traffic analytics per repo → ✅ shipped. `src/lib/traffic.ts` + `src/routes/traffic.tsx`, table `repo_traffic_events` (migration 0020). `track`/`trackView`/`trackClone`/`trackByName` are fire-and-forget; SHA-256 of IP truncated to 16 chars for unique-visitor approximation. Owner-only `GET /:owner/:repo/traffic` renders 7/14/30/90 day windows with an ascii-bar daily chart. Wired into `src/routes/web.tsx` repo overview + `src/routes/git.ts` git-upload-pack handler.
286- **F2** — Org-wide insights → ✅ shipped. `src/routes/org-insights.tsx` exports `computeOrgInsights(orgId)`. `GET /orgs/:slug/insights` requires org membership; aggregates gate green-rate, open/merged PR counts, open issue count, and per-repo rows sorted by activity. No new tables — live rollup across existing `repositories`, `gate_runs`, `pull_requests`, `issues`.
287- **F3** — Admin / superuser panel → ✅ shipped. `src/lib/admin.ts` + `src/routes/admin.tsx`, tables `site_admins` + `system_flags` (migration 0020). `isSiteAdmin(userId)` with bootstrap rule (empty `site_admins` table → oldest user wins); `KNOWN_FLAGS` = { registration_locked, site_banner_text, site_banner_level, read_only_mode }. Routes: `GET /admin` (dashboard), `GET /admin/users` + toggle grant/revoke, `GET /admin/repos` + nuclear delete, `GET /admin/flags` + save. All mutations audit-logged.
288- **F4** — Billing + quotas → ✅ shipped. `src/lib/billing.ts` + `src/routes/billing.tsx`, tables `billing_plans` + `user_quotas` (migration 0020, seeded with free/pro/team/enterprise). `FALLBACK_PLANS` mirror the seeds so billing works pre-migration. Helpers: `getUserQuota` (auto-initialises free row on first read), `bumpUsage`, `checkQuota` (fail-open), `wouldExceedRepoLimit`, `resetIfCycleExpired`. Routes: `GET /settings/billing` (personal view with usage bars + plan cards), `GET /admin/billing` (site-admin plan override), `POST /admin/billing/:userId/plan`.
9ab6971Claude289
290### BLOCK G — Mobile + client
eae38d1Claude291- **G1** — PWA manifest + service worker → ✅ shipped. `src/routes/pwa.ts` serves `/manifest.webmanifest`, `/sw.js`, `/icon.svg`; `Layout` injects `<link rel="manifest">` + a tiny SW registration script. Service worker is network-first for HTML + skips `.git/`/`/api/`/`/login*` routes.
292- **G2** — GraphQL API mirror of REST → ✅ shipped. `src/lib/graphql.ts` is a dependency-free recursive-descent parser + executor over a fixed schema (viewer, user, repository, search, rateLimit). `src/routes/graphql.ts` serves `POST /api/graphql` + a GraphiQL-lite explorer at `GET /api/graphql`. Queries only; writes stay on REST.
293- **G3** — Official CLI (`gluecron`) → ✅ shipped. `cli/gluecron.ts` is a Bun-compilable single-file CLI. Commands: `login`, `whoami`, `repo ls/show/create`, `issues ls`, `gql`, `host`, `version`. Config in `~/.gluecron/config.json` (0600). Talks to the server via REST + GraphQL.
294- **G4** — VS Code extension → ✅ shipped. `vscode-extension/` contains package.json + `src/extension.ts`. Commands: `gluecron.explainFile`, `gluecron.openOnWeb`, `gluecron.searchSemantic`, `gluecron.generateTests`. Detects Gluecron remotes via `git config remote.origin.url`. Settings: `gluecron.host` + `gluecron.token`.
9ab6971Claude295
71cd5ecClaude296### BLOCK I — Filling parity gaps
297- **I1** — Archive / unarchive repository → ✅ shipped. `src/routes/repo-settings.tsx` archive/unarchive toggle (existing `repositories.is_archived` column). `RepoHeader` surfaces an "Archived" badge.
298- **I2** — Template repositories → ✅ shipped. `drizzle/0022_repo_templates.sql` adds `is_template` column + partial index. `src/routes/templates.ts` serves `POST /:owner/:repo/use-template` (git clone --bare into caller's namespace, fresh `activity_feed` entry). Settings UI gains a "Mark as template" toggle. Public repo page renders a prominent "Use this template" CTA for non-owners.
299- **I3** — Repository transfer → ✅ shipped. `drizzle/0022_repo_templates.sql` adds `repo_transfers` audit table. `src/routes/repo-settings.tsx` `POST /:owner/:repo/settings/transfer` (validate target user exists, reject name conflicts, update `owner_id`, log to `repo_transfers`).
300- **I4** — Generic command palette → ✅ shipped. `src/views/layout.tsx` injects a Cmd+K palette with ~20 canonical destinations (Dashboard, Explore, Notifications, Ask AI, Create repo, Marketplace, Installed apps, Register app, Shortcuts, Settings, 2FA, Passkeys, PATs, Billing, Audit, Gists, GraphQL, Admin, Theme). Fuzzy-match, arrow-key navigation, Esc/backdrop to close.
08420cdClaude301- **I5** — Code scanning UI → ✅ shipped. `src/routes/code-scanning.tsx` `GET /:owner/:repo/security` aggregates `gate_runs` matching `%scan%`/`%security%` (last 100), computes latest-per-gate status, renders failed/repaired/total summary cards + per-scanner status list + recent-runs table. Private-repo visibility enforced. Zero new tables — pure surfacing layer.
302- **I6** — Sponsors → ✅ shipped. `drizzle/0023_sponsors.sql` adds `sponsorship_tiers` (maintainer_id, name, monthly_cents, one_time_allowed, is_active) + `sponsorships` (sponsor_id, maintainer_id, tier_id, amount_cents, kind, note, is_public, cancelled_at). `src/routes/sponsors.tsx` serves public `/sponsors/:username` (tier cards + recent public sponsors join) + maintainer `/settings/sponsors` (tier CRUD, soft-retire via is_active=false, activity list). Payment rails deferred — v1 captures intent + thank-you notes.
303- **I7** — Weekly email digest → ✅ shipped. `drizzle/0024_email_digest.sql` adds `users.notify_email_digest_weekly` + `last_digest_sent_at`. `src/lib/email-digest.ts` exposes `composeDigest`/`sendDigestForUser`/`sendDigestsToAll` (never-throws). Pulls notifications + failed/repaired gate_runs + merged PRs from the last 7d, composes escaped HTML + plaintext, and sends via the shared email provider. `/settings/digest/preview` renders the digest inline for self-preview; `/admin/digests` gives site admins a "Send now" trigger + single-user preview, audit-logged as `admin.digests.run`/`admin.digests.preview`.
4c8f666Claude304- **I8** — Symbol / xref navigation → ✅ shipped. `drizzle/0025_code_symbols.sql` adds a `code_symbols` table. `src/lib/symbols.ts` provides a regex-based top-level extractor for ts/js/py/rs/go/rb/java/kt/swift. On-demand indexing via `POST /:owner/:repo/symbols/reindex` walks the default-branch tree, caps at 2000 files/1MB each, replaces the prior set. Browse at `/:owner/:repo/symbols` (A–Z + per-kind counts), search via `/symbols/search?q=`, inspect at `/symbols/:name`. 14 new tests.
4a0dea1Claude305- **I9** — Repository mirroring → ✅ shipped. `drizzle/0026_repo_mirrors.sql` adds `repo_mirrors` (one-per-repo config) + `repo_mirror_runs` (audit log). `src/lib/mirrors.ts` provides URL validation (https/http/git only, no ssh/file/paths/shell metas), credentials-redaction for logs, and `runMirrorSync` that shells out to `git fetch --prune --tags` with a 5-min timeout and `GIT_TERMINAL_PROMPT=0`. `src/routes/mirrors.tsx` serves owner-only `/:owner/:repo/settings/mirror` + site-admin `/admin/mirrors/sync-all`. 17 new tests.
edf7c36Claude306- **I10** — Enterprise SSO via OIDC → ✅ shipped. `drizzle/0027_sso_oidc.sql` adds `sso_config` (singleton `id='default'` row with issuer + authorize/token/userinfo endpoints + client credentials + scopes + optional email-domain allow-list + `auto_create_users` toggle) and `sso_user_links` (maps local user to IdP `sub`, unique per-subject). `src/lib/sso.ts` exposes `buildAuthorizeUrl`/`exchangeCode`/`fetchUserinfo`/`findOrCreateUserFromSso` pure helpers — plain OIDC auth-code flow, no XML / no signature verification dep. `src/routes/sso.tsx` serves site-admin `/admin/sso` config page, `/login/sso` initiator (state + nonce cookies, 10-min TTL), `/login/sso/callback` exchanger + session issuer, plus `POST /settings/sso/unlink` for users. `/login` renders "Sign in with &lt;provider name&gt;" when enabled. 24 new tests (pure helpers + route-auth smokes).
71cd5ecClaude307
8098672Claude308### BLOCK J — Beyond-parity advanced features
309- **J1** — Dependency graph → ✅ shipped. `drizzle/0028_repo_dependencies.sql` adds `repo_dependencies` (ecosystem + name + version_spec + manifest_path + is_dev + commit_sha) with indexes on `(repository_id, ecosystem)` + `(name)`. `src/lib/deps.ts` parses seven manifest formats (package.json, requirements.txt, pyproject.toml, go.mod, Cargo.toml, Gemfile, composer.json) without a TOML library — each parser is defensive and returns `[]` on malformed input. Walks default-branch tree (max 200 manifests, 1MB each), replaces the prior set on reindex. `src/routes/deps.tsx` serves `/:owner/:repo/dependencies` (grouped by ecosystem with per-ecosystem counts) + owner-only `POST /dependencies/reindex`. Reverse-dep lookup via `repositoriesDependingOn(ecosystem, name)` for future "who depends on me" network-graph UI. 21 new tests.
f60ccdeClaude310- **J2** — Security advisories (Dependabot-style) → ✅ shipped. `drizzle/0029_security_advisories.sql` adds `security_advisories` (GHSA + CVE IDs, severity, affected range, fixed version) + `repo_advisory_alerts` (per-repo match state with open/dismissed/fixed, unique on `(repo, advisory, manifest_path)`). `src/lib/advisories.ts` ships a 12-entry seed list (log4shell, lodash, minimist, vm2, urllib3, jwt-go, etc.), a minimal version-range matcher (`satisfiesRange` + `rangeMatches`) that handles `<`/`<=`/`>`/`>=`/`=` clauses including compound ranges, and `scanRepositoryForAlerts(repoId)` which cross-references J1 dep rows against the advisory list — inserts new alerts, reopens fixed-then-regressed ones, auto-closes alerts whose dep went away. `src/routes/advisories.tsx` serves `/:owner/:repo/security/advisories` (open), `/all` (everything), owner-only `POST /scan`, and per-alert `POST /:id/dismiss` + `POST /:id/reopen` with audit-log entries. 27 new tests (version parser, range matcher, seed shape, route auth).
3951454Claude311- **J3** — Commit signature verification (GPG + SSH "Verified" badge) → ✅ shipped. `drizzle/0030_signing_keys.sql` adds `signing_keys` (per-user GPG/SSH pubkeys, unique on `(key_type, fingerprint)`) + `commit_verifications` (memoised per-commit result, unique on `(repo, sha)`). `src/lib/signatures.ts` extracts `gpgsig` / `gpgsig-sha256` from raw commit objects (`getRawCommitObject` added to `src/git/repository.ts`), unarmors PGP + SSH signature blobs, walks OpenPGP packet streams for Issuer Fingerprint (subpacket 33) / Issuer Key ID (subpacket 16), parses the SSHSIG inner publickey field, and SHA-256 fingerprints SSH wire-format keys. Identity matching via fingerprint → optional email check → cached. `src/routes/signing-keys.tsx` serves `GET/POST /settings/signing-keys` + `POST /settings/signing-keys/:id/delete`, audit-logged. `CommitList` + single commit view render a green "Verified" badge when cached `verified=true`. 29 new tests (extraction, unarmor, packet walker, SSH fp, end-to-end fast paths, route auth).
7aa8b99Claude312- **J4** — User following + personalised feed → ✅ shipped. `drizzle/0031_user_follows.sql` adds `user_follows` (composite PK on `(follower_id, following_id)`, CHECK constraint rejecting self-follows, reverse-lookup index on `following_id`). `src/lib/follows.ts` exposes `followUser/unfollowUser/isFollowing/listFollowers/listFollowing/followCounts` + `feedForUser(userId, limit)` which joins `activity_feed` against the follow set (bounded to 200 edges) and filters out private repos the viewer doesn't own. `src/routes/follows.tsx` serves `POST /:user/follow` + `/:user/unfollow` (auth-gated, audit-logged), public `GET /:user/followers` + `/:user/following`, and `GET /feed` (personalised timeline). Follow button + follower/following counts added to the user profile page via `src/routes/web.tsx`. Reserved-name set protects fixed paths (`login`, `settings`, `feed`, etc.). 8 new tests (verb table + route auth).
d412586Claude313- **J5** — Profile READMEs → ✅ shipped. User profile page at `/:owner` now attempts to render `<user>/<user>/README.md` (GitHub convention) or `<user>/.github/README.md` (org-style fallback) as the hero panel above the repo list. No schema changes — reuses `getReadme` / `renderMarkdown` + `repoExists` from the git layer. Failures are silent; missing repo just hides the panel. 2 smoke tests.
9ff7128Claude314- **J6** — Repository rulesets (push policy engine) → ✅ shipped. `drizzle/0032_repo_rulesets.sql` adds `repo_rulesets` (unique on `(repository_id, name)`, enforcement enum active/evaluate/disabled) + `ruleset_rules` (JSON params). `src/lib/rulesets.ts` exposes six rule types (`commit_message_pattern`, `branch_name_pattern`, `tag_name_pattern`, `blocked_file_paths`, `max_file_size`, `forbid_force_push`) + the pure evaluator `evaluatePush(rulesets, ctx) → {allowed, violations}`. Helpers: glob-lite matcher (`globToRegex`), defensive `parseParams`. CRUD: `listRulesetsForRepo`, `getRuleset`, `createRuleset`, `updateRulesetEnforcement`, `deleteRuleset`, `addRule`, `deleteRule`. `src/routes/rulesets.tsx` serves owner-only UI at `/:owner/:repo/settings/rulesets` (list + create), `/:id` (detail, enforcement toggle, add rule), `/:id/delete`, `/:id/rules/:ruleId/delete`. 23 new tests covering each rule type, enforcement modes, glob edge cases, and route-auth redirects.
d62fb36Claude315- **J7** — Closing keywords auto-close issues on PR merge → ✅ shipped. `src/lib/close-keywords.ts` exports pure `extractClosingRefs(text)` and `extractClosingRefsMulti(sources[])` — scans for `(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)[:|-]? #N` with case-insensitive, punctuation-tolerant, word-boundary-respecting matching. Rejects cross-repo refs (`owner/repo#N`), embedded-in-word verbs (`disclose`, `unresolved`), and non-positive numbers. Wired into the PR merge handler in `src/routes/pulls.tsx` — after a successful merge, scans `pr.title + pr.body`, looks up each referenced open issue in the same repo, closes it, and posts a "Closed by pull request #N" comment. Wrapped in try/catch so close-keyword failures never block the merge redirect. 14 new tests covering verb forms, punctuation variants, de-dup+sort, cross-repo rejection, embedded-word rejection, case-insensitivity, and multi-source merging. Total suite 749/749.
0cdfd89Claude316- **J8** — Commit status API (external CI signals) → ✅ shipped. `drizzle/0033_commit_statuses.sql` adds `commit_statuses` (unique on `(repository_id, commit_sha, context)`, state vocabulary pending/success/failure/error). `src/lib/commit-statuses.ts` exposes pure helpers (`isValidSha`, `isValidState`, `sanitiseContext`, `reduceCombined`) and DB helpers (`setStatus` with delete-then-insert upsert, `listStatuses`, `combinedStatus`). `src/routes/commit-statuses.ts` serves `POST /api/v1/repos/:owner/:repo/statuses/:sha` (requireAuth + owner check), `GET /api/v1/repos/:owner/:repo/commits/:sha/statuses` (list, private-repo visibility), `GET /api/v1/repos/:owner/:repo/commits/:sha/status` (combined rollup). Commit detail view now renders a "Checks" pill row when statuses exist, colour-coded per state with clickable target URLs. 18 new tests covering the pure helpers + route auth + invalid-sha rejection. Total suite 767/767.
8ec29ffClaude317- **J13** — Pinned repositories on user profile → ✅ shipped. `drizzle/0035_pinned_repos.sql` adds `pinned_repositories` (unique on `(user_id, repository_id)`, `position` int for explicit ordering). `src/lib/pinned-repos.ts` exposes `MAX_PINS=6`, pure `sanitisePinIds` (de-dup + clamp + trim), `listPinnedForUser` (ordered with owner username joined), `setPinsForUser` (delete-then-insert, filters out private repos the viewer doesn't own), `listPinCandidates`. `src/routes/pinned-repos.tsx` serves `GET/POST /settings/pins` (requireAuth) with a checkbox grid preview. The profile page in `src/routes/web.tsx` now renders a "Pinned" section above the repo grid when the user has any pinned repos; private pins hidden from other viewers. 9 new tests. Total suite 853/853.
bed6b57Claude318- **J14** — Issue dependencies / blocked-by relationships → ✅ shipped. `drizzle/0036_issue_dependencies.sql` adds `issue_dependencies` (`blocker_issue_id`, `blocked_issue_id`, CHECK `issue_dep_no_self`, unique on the pair, indexes on both sides). `src/lib/issue-dependencies.ts` exposes pure `wouldCreateCycle(edges, blocker, blocked)` (BFS following forward blocks edges) + `summariseBlockers` plus DB helpers: `addDependency` (`{ok, reason}` taxonomy rejecting self / cross_repo / exists / cycle / not_found / error), `removeDependency`, `listBlockersOf`, `listBlockedBy` (join issues + users for number/title/state/author). Issue detail page in `src/routes/issues.tsx` now renders a "Dependencies" panel above the body showing Blocked-by + Blocks lists, per-row dismiss buttons (owner/author only), and a `#number`-form to add a new blocker. Two POST routes mirror the J11 pattern: `/:o/:r/issues/:n/dependencies` (add) and `/:o/:r/issues/:n/dependencies/:which/:otherId/remove`. Permission gated by `user.id === owner.id || user.id === issue.authorId`. 14 new tests covering cycle detection (direct + transitive + diamond + deep chains), summariseBlockers counts, and route-auth smokes. Total suite 867/867.
a53ceabClaude319- **J15** — Deterministic release-notes generator → ✅ shipped. `src/lib/release-notes.ts` ships zero-IO helpers: `classifyCommit` (conventional prefix + scope + `!` breaking + trailing `(#N)` PR capture; aliases `feature`/`bugfix`/`doc`/`tests`; Merge-commit detection for pull requests and branches), `groupCommits`, `contributorsFrom`, `renderNotesMarkdown` (Breaking-changes section first, then 13 ordered buckets, then Contributors + Full-Changelog compare link). `src/routes/releases.tsx` adds `POST /:owner/:repo/releases/generate-notes` which re-renders the new-release form with notes pre-filled (preserves tag/name/target/draft/prerelease fields) plus a notice banner on missing commits or resolve failure. AI-disabled repos now fall through to the deterministic renderer on publish instead of writing empty notes. 30 new tests. Total suite 897/897.
21ff9beClaude320- **J16** — PR auto-merge when checks pass → ✅ shipped. `drizzle/0037_pr_auto_merge.sql` adds `pr_auto_merge` (unique on `pull_request_id`, merge-method text, commit-title/message overrides, last_status + last_checked_at + notified_ready columns for the status-hook watcher). `src/lib/pr-auto-merge.ts` is the pure state machine + DB accessor: `computeAutoMergeAction({autoMergeEnabled, prState, isDraft, combinedState, totalChecks}) → {action: wait|merge|skip, reason}` covers all nine paths (not_enabled / pr_closed / pr_draft / no_checks / checks_pending / checks_failed / checks_passed). `src/lib/pr-auto-merge-trigger.ts` is called fire-and-forget from the commit-status POST — resolves each opted-in PR's head branch, matches against the incoming SHA, runs `combinedStatus` + the pure decider, and on first green transition posts a PR comment + PR-author notification. PR detail page gets an `AutoMergePanel` with live status colour (green/yellow/red), merge-method selector (merge/squash/rebase), and disable button. 16 new tests. Total suite 913/913.
7c0203eClaude321- **J22** — Code review suggestion blocks (``` ```suggestion ```) → ✅ shipped. `src/lib/code-suggestions.ts` is pure: `extractSuggestions(body)` parses fenced `suggestion` blocks with CommonMark parity (indented opener up to 3 spaces, 3+ backtick fences for content containing triple-backticks, case-sensitive language token, skips unterminated). `applySuggestionToContent({content,startLine,endLine,suggestion})` returns `{ok, content?, reason?}` with a stable error taxonomy (`bad_range` / `line_out_of_bounds` / `empty_content` / `no_change`). Preserves the original file's line-ending (CRLF wins if any present) and trailing-newline presence. `src/routes/code-suggestions.tsx` serves `POST /:owner/:repo/pulls/:number/comments/:commentId/apply-suggestion` (requireAuth, owner/PR-author/comment-author only, open-PR only, comment must have `file_path` + `line_number`). Commits via `hash-object` → `ls-tree -r` rewrite → `mktree` → `commit-tree -p` → `update-ref`. Commit message: `Apply suggestion from #N\n\nCo-authored-by: …`. PR detail page in `src/routes/pulls.tsx` renders a "Commit suggestion" button below every eligible comment (multiple buttons when a comment has >1 block). 33 new tests covering line-ending detection, extractor edges (empty/unterminated/multi-block/4-backtick/indented info/case-sensitivity), applier (single line / multi-line / range / CRLF / no-trailing-newline / empty suggestion deletes / bad-range + out-of-bounds + no-op rejections), `applyNthSuggestion`, `__internal` parity, route auth smokes. Total suite 1068/1068.
f3e1844Claude322- **J21** — CODEOWNERS validator → ✅ shipped. `src/lib/codeowners-lint.ts` lints a CODEOWNERS file with a pure-first API — `lexCodeowners` preserves 1-indexed line numbers while stripping comments, `classifyOwnerToken` (GitHub-parity regexes for user / team / email / invalid), `isPlausiblePattern` (unbalanced brackets / whitespace), async `validateCodeowners(content, resolver)` taking an `OwnerResolver` so the core stays IO-free. Report emits findings sorted by line then severity plus derived `errors/warnings/infos/ok`. Stable code taxonomy: `empty_file`, `no_owners`, `empty_pattern`, `bad_pattern_syntax`, `bad_owner_format`, `unknown_user`, `unknown_team`, `duplicate_pattern`, `duplicate_owner`, `missing_catchall`. `src/routes/codeowners-lint.tsx` serves `GET /:owner/:repo/codeowners` — softAuth, private repos 404. Walks the three standard paths on the default branch, builds a per-request memoising resolver against `users.username` + `organizations.slug` + `teams.slug`, renders four summary cards + findings list + line-numbered file body, or an empty-state with "Create .github/CODEOWNERS" CTA. 28 new tests covering lexer (blank/comment/inline-comment/CRLF/team tokens), owner classifier (user/team/email/invalid edges), pattern plausibility, validator (empty file, clean catchall, pattern-no-owners, unknown user, unknown team, email passthrough, bad format, duplicate patterns, duplicate owners, ordering, ok-false on error, info-only still ok, bracket-unbalanced), `__internal` parity, route 404. Total suite 1035/1035.
d6b4e67Claude323- **J20** — Stale issue detector → ✅ shipped. `src/lib/stale-issues.ts` is a pure filter/bucketer — `STALE_PERIODS = ['30d','60d','90d','180d']`, `parsePeriod` with default-on-garbage, `filterStale(issues, now, thresholdDays)` (open-only, parseable-dates-only, oldest-first), `bucketByStaleness` (30-60 / 60-90 / 90-180 / 180+ with inclusive lower bounds), `buildStaleReport` one-shot builder. Accepts both Date and ISO inputs; drops unparseable timestamps silently. `src/routes/stale-issues.tsx` serves `GET /:owner/:repo/issues/stale[?period=…]` (softAuth, read-only), fetches open issues (2000 cap) with a grouped comment count, and renders four bucket cards + an age-coloured table (amber >=90d, red >=180d). Route is mounted BEFORE `issueRoutes` so the static `/issues/stale` path wins over `/issues/:number`. `resolveRepo` wrapped in try/catch — DB failure 404s, never 500. Issues-list toolbar gets a new "Stale" button. Non-destructive: surfaces staleness only; no auto-close. 22 new tests covering period parsing, filter threshold semantics + boundary inclusion + oldest-first sort, bad-date tolerance, bucket boundaries, `buildStaleReport`, `__internal` parity, route smokes. Total suite 1007/1007.
26484ebClaude324- **J19** — Atom / RSS feeds for commits, releases, issues → ✅ shipped. `src/lib/atom-feed.ts` is a pure Atom 1.0 renderer — `escapeXml` (five XML metacharacters), `toIsoUtc` (Date + ISO, epoch fallback), `renderAtomFeed` (XML-decl + `<feed xmlns>` + feed metadata + entries with `<id>/<title>/<link>/<updated>/<published>/<author>/<summary>/<content>`), `ATOM_CONTENT_TYPE`. `pickFeedUpdated` auto-derives from newest entry when not set explicitly. `src/routes/feeds.ts` serves three feeds — `/:owner/:repo/{commits,releases,issues}.atom`, each capped at 50 entries. `softAuth`; private repos receive an empty-feed response so external readers stay non-crashing. Cache: `public, max-age=60, stale-while-revalidate=300`. Commits sourced from `listCommits` on the default branch; releases filter out drafts; issues span both open + closed, newest-first. 20 new tests covering escape edge cases, toIsoUtc, feed structure, well-formedness with zero entries, metacharacter safety, newest-entry auto-update, explicit updatedAt respect, route content-type + cache headers. Total suite 985/985.
7c975c6Claude325- **J18** — Repository pulse / activity summary → ✅ shipped. `src/lib/repo-pulse.ts` rolls already-fetched commits + PR + issue rows into time-windowed buckets: `parseWindow` + `windowStart` (1d / 7d / 30d / 90d), `summariseCommits` (in-window filter, email-lowercased author-grouping, count-desc/name-asc sort, first/last SHA), `summarisePrs` (opened/mergedCount/closed/active + openedList + mergedList), `summariseIssues` (opened/closed/active + openedList + closedList), `buildPulseReport` one-shot. `src/routes/pulse.tsx` serves `GET /:owner/:repo/pulse[?window=…]` (softAuth, read-only); fetches up to 500 commits via `listCommits` + 1000 PRs + 1000 issues in parallel; renders eight KPI cards, top-10 contributors, recent merges + newly-opened/closed issues. `resolveRepo` wrapped in try/catch so DB failure falls through to 404, mirroring the J12 community page's never-500 guarantee. Insights page header links to Pulse. 23 new tests covering window parsing, date-edge filters, author grouping, PR/issue bucketing with Date + ISO inputs, bad-date tolerance, `buildPulseReport` + `__internal` re-exports + route smokes. Total suite 965/965.
9a4eb42Claude326- **J17** — Multi-template issue selector → ✅ shipped. Extends the single-file `ISSUE_TEMPLATE.md` loader with full GitHub-parity `.github/ISSUE_TEMPLATE/*.md` directory support (plus `.gluecron/ISSUE_TEMPLATE/` fallback, upper + lowercase variants). `src/lib/issue-templates.ts` is pure-first: `splitFrontmatter` + `parseFrontmatterMeta` parse the narrow YAML subset used by issue-template frontmatter (flat `key: value`, flow-lists `[a,"b",c]`, block-lists `- a`, single+double quote stripping, case-insensitive keys). `slugFromFilename` normalises the file basename into a URL-safe slug, clamped to 64 chars. `buildTemplateFromFile` merges filename + meta + body into a typed `IssueTemplate`. `listIssueTemplates(owner, repo)` scans the four standard dirs on the default branch, filters `.md|.markdown` (excludes `config.*`), caps at 20 templates, dedupes by slug, and swallows all git failures to `[]`. `findTemplateBySlug` finds the active pick. `src/routes/issues.tsx` new-issue GET handler renders a chooser page when 2+ templates exist; picked template prefills the title input (from frontmatter `title:`) + body textarea (from body) + label pills. Single template auto-picks; `?template=__blank` escape hatch skips prefill. Falls back to the legacy `ISSUE_TEMPLATE.md` loader when no frontmatter templates are found. 29 new tests covering frontmatter split (with/without fence, CRLF tolerance, trailing whitespace on closing fence), flat/flow-list/block-list parsing, slug normalisation + unicode, `buildTemplateFromFile` (with/without frontmatter, empty dir path), `findTemplateBySlug` null/unknown/exact, `__internal` re-exports, and route-auth smokes. Total suite 942/942.
f9e7c01Claude327- **J12** — Community profile / health scorecard → ✅ shipped. `GET /:owner/:repo/community` renders a GitHub-parity "Community standards" page scoring the repo on 8 checklist items: description, README, LICENSE (all required), CODE_OF_CONDUCT, CONTRIBUTING, issue templates, PR template, topics (recommended). `src/lib/community.ts` exposes pure matchers (`isReadme`, `isLicense`, `isCodeOfConduct`, `isContributing`, `isPrTemplate`), pure `checklistFromInputs` (drives all the I/O-free unit tests), `buildReport` (→ percent + required breakdown), and `computeHealth` (reads default-branch root tree + `.github/` subtree + repo metadata + topics). Each missing item offers a one-click "Add <path>" or "Edit settings" link. `src/routes/community.tsx` degrades to a zero-score report on git/DB failure — never 500s. 21 new tests. Total suite 844/844.
3247f79Claude328- **J11** — PR auto-assign reviewers from CODEOWNERS + requested-reviewers tracking → ✅ shipped. `drizzle/0034_pr_review_requests.sql` adds `pr_review_requests` (unique on `(pull_request_id, reviewer_id)`, source enum `codeowners|manual|ai`, state enum `pending|approved|changes_requested|dismissed`). `src/lib/review-requests.ts` exposes pure helpers (`isValidSource`, `isValidState`, `nextState`, `sanitiseCandidates`) and DB helpers (`requestReviewers` idempotent, `listForPr` with username join, `dismissRequest`, `recordReviewOutcome`, `autoAssignFromCodeowners`, `countPendingForUser`). On PR creation, `src/routes/pulls.tsx` runs `git diff --numstat base...head` to extract changed paths, calls `reviewersForChangedFiles` (Block B3 CODEOWNERS parser), resolves usernames → user IDs, excludes the PR author, and fires `review_requested` notifications. PR detail page renders a `ReviewersPanel` with per-reviewer state pills, source labels, and dismiss + manual-add forms for owner/author. Auto-assign runs fire-and-forget — CODEOWNERS failures never block PR creation. 17 new tests. Total suite 823/823.
329- **J10** — Repository status badges (shields.io-style SVG) → ✅ shipped. `src/lib/badge.ts` renders shields.io-style flat two-segment badges with zero IO — exports `renderBadge`, `escapeXml`, `estimateTextWidth` (Verdana-11 heuristic), `colorForState`. Named colour table (green/red/yellow/blue/grey/orange) + hex-literal passthrough. Label + value clamped to 64 chars. `src/routes/badges.ts` serves `/:o/:r/badge/gates.svg` (latest 20 gate_runs rollup → passing/running/failing), `/issues.svg` + `/prs.svg` (open counts), `/status.svg` (combined commit status on default-branch HEAD), `/status/:context.svg` (single named context). Every handler wrapped in try/catch and returns a grey "unknown" badge on DB or git failure — never 500. `image/svg+xml; charset=utf-8`, `Cache-Control: public, max-age=60, stale-while-revalidate=300`. `softAuth` so public-repo badges don't require cookies. 21 new tests. Total suite 806/806.
4438e31Claude330- **J9** — GitHub-style contribution heatmap on user profile → ✅ shipped. `src/lib/contribution-heatmap.ts` exposes pure `buildHeatmap(activities, windowDays=365, today?)` that returns a 53-week Sunday-aligned grid of `{date, count, level 0-4, dow}` cells plus `totalContributions`, `maxDayCount`, `longestStreak`, `currentStreak`, and window start/end dates. `levelFor(count, max)` buckets into 5 GitHub-style quartiles. Wired into the profile handler in `src/routes/web.tsx` — queries `activity_feed` rows authored by the user over the last 365 days and renders a scrollable 11×11 px cell grid with hover titles, a legend, and streak counters. No schema changes — reuses existing `activity_feed` rows. 18 new tests. Total suite 785/785.
8098672Claude331
9ab6971Claude332### BLOCK H — Marketplace
06139e6Claude333- **H1** — App marketplace → ✅ shipped. `src/routes/marketplace.tsx` + `src/lib/marketplace.ts` + `drizzle/0021_marketplace_and_apps.sql` (5 tables: `apps`, `app_installations`, `app_bots`, `app_install_tokens`, `app_events`). Routes: `GET /marketplace` (public directory with search), `GET /marketplace/:slug` (detail + install CTA), `POST /marketplace/:slug/install` (user-target install in v1), `POST /marketplace/installations/:id/uninstall`, `GET /settings/apps` (personal list), `GET+POST /developer/apps-new` (register), `GET /developer/apps/:slug/manage` (event log + install count), `POST /developer/apps/:slug/tokens/new` (show-once token). Install idempotent via soft-update on existing non-uninstalled row.
334- **H2** — GitHub Apps equivalent (bot identities + installation tokens) → ✅ shipped. Same schema as H1: every app gets a `<slug>[bot]` row in `app_bots`. `generateBearerToken()` produces `ghi_`-prefixed bearers; `hashBearer` (sha256) is the only form persisted. `verifyInstallToken(token)` returns `{installation, app, botUsername, permissions}` or `null` (checks revoked/expired/uninstalled/suspended). Permission vocabulary: `contents:read/write`, `issues:read/write`, `pulls:read/write`, `checks:read/write`, `deployments:read/write`, `metadata:read` — `hasPermission` implements write→read implication.
9ab6971Claude335
336---
337
338## 4. LOCKED BLOCKS (DO NOT UNDO)
339
340Everything below is committed, tested, and load-bearing. **Do not delete, rename, or semantically change without owner permission.**
341
342### 4.1 Infrastructure (locked)
343- `src/app.tsx` — route composition, middleware order, error handlers
344- `src/index.ts` — Bun server entry
345- `src/lib/config.ts` — env getters (late-binding)
21ff9beClaude346- `src/db/schema.ts` — 99 tables. New tables only via new migration.
9ab6971Claude347- `src/db/index.ts` — lazy proxy DB connection
348- `src/db/migrate.ts` — migration runner
349- `drizzle/0000_initial.sql`, `drizzle/0001_green_ecosystem.sql` — migrations
058d752Claude350- `drizzle/0004_org_owned_repos.sql` (Block B2) — migration, never edited in place
351- `drizzle/0005_totp_2fa.sql` (Block B4) — migration, never edited in place
352- `drizzle/0006_webauthn_passkeys.sql` (Block B5) — migration, never edited in place
353- `drizzle/0007_oauth_provider.sql` (Block B6) — migration, never edited in place
5e888b7Claude354- `drizzle/0008_workflows.sql` (Block C1) — migration, never edited in place
25a91a6Claude355- `drizzle/0009_packages.sql` (Block C2) — migration, never edited in place
356- `drizzle/0010_pages.sql` (Block C3) — migration, never edited in place
357- `drizzle/0011_environments.sql` (Block C4) — migration, never edited in place
3cbe3d6Claude358- `drizzle/0012_ai_native.sql` (Block D) — migration, never edited in place. Adds `codebase_explanations`, `dep_update_runs`, `code_chunks`.
1e162a8Claude359- `drizzle/0013_discussions.sql` (Block E2) — migration, never edited in place. Adds `discussions`, `discussion_comments`.
360- `drizzle/0014_gists.sql` (Block E4) — migration, never edited in place. Adds `gists`, `gist_files`, `gist_revisions`, `gist_stars`.
361- `drizzle/0015_projects.sql` (Block E1) — migration, never edited in place. Adds `projects`, `project_columns`, `project_items`.
362- `drizzle/0016_wikis.sql` (Block E3) — migration, never edited in place. Adds `wiki_pages`, `wiki_revisions`.
a79a9edClaude363- `drizzle/0017_merge_queue.sql` (Block E5) — migration, never edited in place. Adds `merge_queue_entries` (with partial unique index on `pull_request_id WHERE state IN ('queued','running')`).
364- `drizzle/0018_required_checks.sql` (Block E6) — migration, never edited in place. Adds `branch_required_checks`.
365- `drizzle/0019_protected_tags.sql` (Block E7) — migration, never edited in place. Adds `protected_tags`.
8f50ed0Claude366- `drizzle/0020_analytics_and_admin.sql` (Block F) — migration, never edited in place. Adds `repo_traffic_events`, `system_flags`, `site_admins`, `billing_plans` (seeded free/pro/team/enterprise), `user_quotas`.
06139e6Claude367- `drizzle/0021_marketplace_and_apps.sql` (Block H) — migration, never edited in place. Adds `apps`, `app_installations` (partial unique index on `(app_id, target_type, target_id) WHERE uninstalled_at IS NULL`), `app_bots` (one-per-app, `<slug>[bot]` username), `app_install_tokens` (sha256 hash, expires_at, revoked_at), `app_events` (audit trail).
71cd5ecClaude368- `drizzle/0022_repo_templates.sql` (Block I2+I3) — migration, never edited in place. Adds `repositories.is_template` (partial index where true) + `repo_transfers` audit table.
08420cdClaude369- `drizzle/0023_sponsors.sql` (Block I6) — migration, never edited in place. Adds `sponsorship_tiers` + `sponsorships` tables.
370- `drizzle/0024_email_digest.sql` (Block I7) — migration, never edited in place. Adds `users.notify_email_digest_weekly` + `users.last_digest_sent_at`.
4c8f666Claude371- `drizzle/0025_code_symbols.sql` (Block I8) — migration, never edited in place. Adds `code_symbols` table with indexes on `(repository_id, name)` + `(repository_id, path)`.
4a0dea1Claude372- `drizzle/0026_repo_mirrors.sql` (Block I9) — migration, never edited in place. Adds `repo_mirrors` (unique on `repository_id`) + `repo_mirror_runs`.
edf7c36Claude373- `drizzle/0027_sso_oidc.sql` (Block I10) — migration, never edited in place. Adds `sso_config` singleton (`id='default'`) + `sso_user_links` (`subject` unique, FK to `users` with ON DELETE CASCADE).
8098672Claude374- `drizzle/0028_repo_dependencies.sql` (Block J1) — migration, never edited in place. Adds `repo_dependencies` with indexes on `(repository_id, ecosystem)` + `(name)`.
f60ccdeClaude375- `drizzle/0029_security_advisories.sql` (Block J2) — migration, never edited in place. Adds `security_advisories` (`ghsa_id` unique) + `repo_advisory_alerts` (unique on `(repository_id, advisory_id, manifest_path)`, status index).
3951454Claude376- `drizzle/0030_signing_keys.sql` (Block J3) — migration, never edited in place. Adds `signing_keys` (unique on `(key_type, fingerprint)`) + `commit_verifications` (unique on `(repository_id, commit_sha)`).
7aa8b99Claude377- `drizzle/0031_user_follows.sql` (Block J4) — migration, never edited in place. Adds `user_follows` (composite PK on `(follower_id, following_id)`, CHECK no-self-follow, reverse index on `following_id`).
9ff7128Claude378- `drizzle/0032_repo_rulesets.sql` (Block J6) — migration, never edited in place. Adds `repo_rulesets` (unique on `(repository_id, name)`, enforcement enum) + `ruleset_rules` (JSON params).
0cdfd89Claude379- `drizzle/0033_commit_statuses.sql` (Block J8) — migration, never edited in place. Adds `commit_statuses` (unique on `(repository_id, commit_sha, context)`, state vocabulary pending/success/failure/error).
3247f79Claude380- `drizzle/0034_pr_review_requests.sql` (Block J11) — migration, never edited in place. Adds `pr_review_requests` (unique on `(pull_request_id, reviewer_id)`, source enum codeowners/manual/ai, state enum pending/approved/changes_requested/dismissed, reviewer+state index for inbox queries).
8ec29ffClaude381- `drizzle/0035_pinned_repos.sql` (Block J13) — migration, never edited in place. Adds `pinned_repositories` (unique on `(user_id, repository_id)`, `(user_id, position)` index for ordered listing).
bed6b57Claude382- `drizzle/0036_issue_dependencies.sql` (Block J14) — migration, never edited in place. Adds `issue_dependencies` (CHECK `issue_dep_no_self`, unique on `(blocker_issue_id, blocked_issue_id)`, indexes on both sides). Same-repo constraint enforced at application layer.
21ff9beClaude383- `drizzle/0037_pr_auto_merge.sql` (Block J16) — migration, never edited in place. Adds `pr_auto_merge` (unique on `pull_request_id`, `merge_method` text, `commit_title`/`commit_message` overrides, `last_status` + `last_checked_at` + `notified_ready` for the status-hook watcher).
9ab6971Claude384
385### 4.2 Git layer (locked)
386- `src/git/repository.ts` — tree / blob / commits / diff / branches / blame / search / raw / tags / commitsBetween
387- `src/git/protocol.ts` — Smart HTTP pkt-line
388- `src/hooks/post-receive.ts` — CODEOWNERS sync, gates, auto-deploy, webhook fan-out
389
390### 4.3 Auth + security (locked)
391- `src/lib/auth.ts` — bcrypt, session tokens
25a91a6Claude392- `src/middleware/auth.ts` — softAuth + requireAuth. Accepts three auth inputs: session cookie (web), OAuth access token (`glct_` prefix, Block B6), and personal access token (`glc_` prefix, Block C2). Invalid bearer → 401 JSON. Cookie flow → /login redirect.
9ab6971Claude393- `src/middleware/rate-limit.ts` — fixed-window limiter
394- `src/middleware/request-context.ts` — request-ID
395- `src/lib/security-scan.ts` — `SECRET_PATTERNS` (exported) + `scanForSecrets` + `aiSecurityScan`
058d752Claude396- `src/lib/codeowners.ts` — parser + `ownersForPath` (last-match-wins); team expansion helpers for `@org/team` (Block B3)
397- `src/lib/totp.ts` (Block B4) — TOTP enroll / verify / recovery codes
398- `src/lib/webauthn.ts` (Block B5) — WebAuthn registration + assertion helpers
399- `src/lib/oauth.ts` (Block B6) — OAuth 2.0 provider: authorization code grant, token issuance, scope enforcement
5e888b7Claude400- `src/lib/workflow-parser.ts` (Block C1) — YAML subset parser for `.gluecron/workflows/*.yml`. Exports `parseWorkflow(src)` returning `{ ok, workflow | error }`. Never throws.
401- `src/lib/workflow-runner.ts` (Block C1) — shell executor. Exports `executeRun`, `drainOneRun`, `enqueueRun`, `startWorker`. Clones repo to tmpdir, runs each job via `Bun.spawn(["bash","-c",step.run])` with SIGTERM→SIGKILL timeouts, size-capped stdout/stderr, cleans up in `finally`.
25a91a6Claude402- `src/lib/packages.ts` (Block C2) — npm protocol helpers: `parsePackageName`, `computeShasum` (sha1), `computeIntegrity` (sha512 base64), `buildPackument`, `resolveRepoFromPackageJson`, `parseRepoUrl`, `tarballFilename`. Pure functions.
403- `src/lib/pages.ts` (Block C3) — `onPagesPush` (never throws), `resolvePagesPath` (probe list including pretty URLs + traversal strip), `contentTypeFor` (MIME).
404- `src/lib/environments.ts` (Block C4) — `matchGlob`, `listEnvironments`, `getOrCreateEnvironment`, `getEnvironmentByName`, `isReviewer`, `reviewerIdsOf`, `allowedBranchesOf`, `computeApprovalState`, `reduceApprovalState`, `recordApproval`, `requiresApprovalFor`. Empty reviewers list → repo owner approves. Any rejection hard-stops.
9ab6971Claude405
406### 4.4 AI layer (locked)
407- `src/lib/ai-client.ts` — Anthropic client + model constants
3cbe3d6Claude408- `src/lib/ai-generators.ts` — commit / PR / changelog / issue-triage / **pull-request-triage (D3)**
9ab6971Claude409- `src/lib/ai-chat.ts` — conversational chat
410- `src/lib/ai-review.ts` — PR code review
411- `src/lib/auto-repair.ts` — worktree-backed repair commits
412- `src/lib/merge-resolver.ts` — AI merge conflict resolution
3cbe3d6Claude413- `src/lib/ai-explain.ts` (Block D6) — `explainCodebase(...)` + `getCachedExplanation(...)`. Samples up to ~25 representative files (~60KB cap), Sonnet 4, upserts into `codebase_explanations`. Fallback to README-ish synthesis when no key. Never throws.
414- `src/lib/ai-completion.ts` (Block D9) — `completeCode({prefix, suffix?, language?, maxTokens?, repoHint?})` via Haiku. Inline LRU (size 200, 5-min TTL) keyed on sha256 of prefix+suffix+language. Code-fence stripping. Never throws. `__test` bundle exposed.
415- `src/lib/dep-updater.ts` (Block D2) — `parseManifest`, `queryNpmLatest`, `planUpdates` (injectable `fetchLatest`), `applyBumps`, `runDepUpdateRun`. Creates `gluecron/dep-update-<ts>` branch via git plumbing + opens a PR row. Never throws.
416- `src/lib/semantic-search.ts` (Block D1) — `tokenize`, `hashEmbed` (512-dim L2-normalised FNV-1a + sign trick), `embedBatch` (Voyage `voyage-code-3` when `VOYAGE_API_KEY` set, else fallback), `chunkFile`, `isCodeFile`, `indexRepository`, `searchRepository`, `cosine`, `isEmbeddingsProviderAvailable`, `__test` bundle.
1e162a8Claude417- `src/lib/ai-incident.ts` (Block D4) — `onDeployFailure({deploymentId, reason, logs?})` and pure helper `summariseCommitsForIncident(commits)`. Sonnet 4 structured JSON RCA → opens `issues` row, attaches `incident` label if present, sets `deployments.blockedReason`. Never throws; deterministic fallback body when no API key. Wired from `post-receive.ts triggerCrontechDeploy` + `deployments.tsx retry-incident`.
418- `src/lib/ai-tests.ts` (Block D8) — pure helpers `detectLanguage`, `detectTestFramework`, `buildTestsPrompt`, `suggestedTestPath`, `generateTestStub`, `contentTypeFor`. Returns `{code:"", framework:"fallback"}` on no API key. Never throws.
419- `src/lib/branch-protection.ts` (Block D5) — `matchProtection(repoId, branch)` (exact wins; deterministic glob sort), `evaluateProtection(rule, ctx)` (pure — checks `requireAiApproval | requireGreenGates | requireHumanReview | requiredApprovals`), `countHumanApprovals(prId)` (LGTM/+1/approved heuristic). Never throws. Enforcement is in `src/routes/pulls.tsx` merge handler, after existing hard-gate filter.
9ab6971Claude420
421### 4.5 Platform (locked)
24cf2caClaude422- `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.
423- `src/lib/email.ts` — provider-pluggable email sender (`log`|`resend`). `sendEmail()` never throws. `absoluteUrl()` joins paths against `APP_BASE_URL`.
424- `src/lib/templates.ts` — `loadIssueTemplate` / `loadPrTemplate`. Checks standard paths (`.github/`, `.gluecron/`, root, `docs/`) on the default branch, strips YAML frontmatter, 16KB cap, returns null on any failure.
9ab6971Claude425- `src/lib/unread.ts` — unread count helper (never throws)
426- `src/lib/repo-bootstrap.ts` — green defaults on repo creation
427- `src/lib/gate.ts` — gate orchestration + persistence
428- `src/lib/cache.ts` — LRU cache, git-cache invalidation
6fc53bdClaude429- `src/lib/reactions.ts` — `summariseReactions`, `toggleReaction`, `ALLOWED_EMOJIS`, `EMOJI_GLYPH`, `isAllowedEmoji`, `isAllowedTarget`
9ab6971Claude430
431### 4.6 Routes (locked endpoints — behaviour must be preserved)
432- `src/routes/git.ts` — Smart HTTP (clone/push)
433- `src/routes/api.ts` — REST (`POST /api/repos`, `GET /api/users/:u/repos`, `GET /api/repos/:o/:n`, `POST /api/setup`)
ad6d4adClaude434- `src/routes/hooks.ts` — `POST /api/hooks/gatetest` (bearer/HMAC), `GET /api/hooks/ping`, `POST /api/v1/gate-runs` (PAT backup), `GET /api/v1/gate-runs`. See `GATETEST_HOOK.md`.
6fc53bdClaude435- `src/routes/theme.ts` — `GET /theme/toggle`, `GET /theme/set?mode=`. Writes `theme` cookie (`dark`|`light`, 1-year). Layout reads via pre-paint inline script.
436- `src/routes/audit.tsx` — `GET /settings/audit` (personal) + `GET /:owner/:repo/settings/audit` (owner-only).
24cf2caClaude437- `src/routes/saved-replies.tsx` — `GET/POST /settings/replies`, `POST /settings/replies/:id`, `POST /settings/replies/:id/delete`, `GET /api/user/replies`. Unique constraint `saved_replies_user_shortcut`.
438- `src/routes/deployments.tsx` — `GET /:owner/:repo/deployments` (grouped by env, success-rate rollup), `GET /:owner/:repo/deployments/:id` (detail).
6fc53bdClaude439- `src/routes/reactions.ts` — `POST /api/reactions/:targetType/:targetId/:emoji/toggle` (authed, form- or fetch-compatible), `GET /api/reactions/:targetType/:targetId`. Targets: `issue|pr|issue_comment|pr_comment`. Emojis: 8 canonical.
9ab6971Claude440- `src/routes/auth.tsx` — register / login / logout
441- `src/routes/web.tsx` — home / new / browse / blob / commits / raw / blame / star / search / profile
442- `src/routes/issues.tsx` — issue CRUD + comments + labels + lock
443- `src/routes/pulls.tsx` — PR CRUD + review + merge + close
444- `src/routes/editor.tsx` — web file editor
445- `src/routes/compare.tsx` — base...head diff
24cf2caClaude446- `src/routes/settings.tsx` — profile + password + email notification preferences (`POST /settings/notifications`)
9ab6971Claude447- `src/routes/repo-settings.tsx` — repo settings + delete
448- `src/routes/webhooks.tsx` — webhook CRUD + test + `fireWebhooks`
449- `src/routes/fork.ts` — fork
450- `src/routes/explore.tsx` — discover
451- `src/routes/tokens.tsx` — personal access tokens
452- `src/routes/contributors.tsx` — contributor list
453- `src/routes/notifications.tsx` — inbox + unread API
454- `src/routes/dashboard.tsx` — authed home (`renderDashboard` exported)
455- `src/routes/ask.tsx` — global + repo AI chat + explain
456- `src/routes/releases.tsx` — tags + AI changelog
457- `src/routes/gates.tsx` — history + settings + branch protection UI
458- `src/routes/insights.tsx` — insights + milestones
459- `src/routes/search.tsx` — global search + `/shortcuts`
460- `src/routes/health.ts` — `/healthz` `/readyz` `/metrics`
6563f0aClaude461- `src/routes/orgs.tsx` — `/orgs` list, `/orgs/new` create, `/orgs/:slug` profile, `/orgs/:slug/people` + add/role/remove, `/orgs/:slug/teams` + create, `/orgs/:slug/teams/:teamSlug` + member add/remove. All require auth. Role guards via `orgRoleAtLeast`; last-owner cannot be demoted or removed; every write path `audit()`'d.
058d752Claude462- `src/lib/orgs.ts` (Block B1) — `isValidSlug` (rejects reserved + too-short/long + consecutive/leading/trailing hyphens), `normalizeSlug`, `orgRoleAtLeast` (owner>admin>member), `isValidOrgRole`, `isValidTeamRole`, `loadOrgForUser`, `listOrgsForUser`, `listOrgMembers`, `listTeamsForOrg`, `listTeamMembers`, `__test` export for unit tests.
463- `src/routes/settings-2fa.tsx` (Block B4) — TOTP enroll / verify / disable + recovery codes UI. All require auth.
464- `src/routes/passkeys.tsx` (Block B5) — WebAuthn passkey registration / assertion / management. All require auth.
465- `src/routes/oauth.tsx` (Block B6) — OAuth 2.0 authorize + token + userinfo endpoints.
466- `src/routes/developer-apps.tsx` (Block B6) — developer-facing OAuth app CRUD (`/settings/developer/apps`), client secret rotation, audit-logged.
5e888b7Claude467- `src/routes/workflows.tsx` (Block C1) — Actions UI. `GET /:owner/:repo/actions`, `GET /:owner/:repo/actions/runs/:runId`, `POST /:owner/:repo/actions/:workflowId/run` (auth+owner), `POST /:owner/:repo/actions/runs/:runId/cancel` (auth+owner). Manual runs are `event=manual`, ref=default branch.
25a91a6Claude468- `src/routes/packages-api.ts` (Block C2) — npm protocol: `GET/PUT/DELETE /npm/*` (packument, tarball, publish, yank); JSON helpers at `/api/packages/:owner/:repo/...`. PAT (`glc_`) bearer auth.
469- `src/routes/packages.tsx` (Block C2) — UI: `/:owner/:repo/packages` list + `/:owner/:repo/packages/:pkgName` detail.
470- `src/routes/pages.tsx` (Block C3) — `GET /:owner/:repo/pages/*` serves static files from latest gh-pages commit (binary via `getRawBlob`, text via `getBlob`). `GET/POST /:owner/:repo/settings/pages` settings + redeploy.
471- `src/routes/environments.tsx` (Block C4) — settings CRUD at `/:owner/:repo/settings/environments`; approval endpoints at `/:owner/:repo/deployments/:id/{approve,reject}`.
3cbe3d6Claude472- `src/routes/ai-explain.tsx` (Block D6) — `GET /:owner/:repo/explain` (softAuth), `POST /:owner/:repo/explain/regenerate` (requireAuth, owner-only).
473- `src/routes/ai-changelog.tsx` (Block D7) — `GET /:owner/:repo/ai/changelog` (softAuth). Form + rendered output; `?format=markdown` returns `text/markdown`.
474- `src/routes/copilot.ts` (Block D9) — `POST /api/copilot/completions` (requireAuth, 60/min rate limit), `GET /api/copilot/ping` (public).
475- `src/routes/dep-updater.tsx` (Block D2) — `GET /:owner/:repo/settings/dep-updater` + `POST /:owner/:repo/settings/dep-updater/run` (requireAuth, owner-only).
476- `src/routes/semantic-search.tsx` (Block D1) — `GET /:owner/:repo/search/semantic?q=` (softAuth) + `POST /:owner/:repo/search/semantic/reindex` (requireAuth, owner-only).
1e162a8Claude477- `src/routes/ai-tests.tsx` (Block D8) — `GET /:owner/:repo/ai/tests` (softAuth form + picker), `GET /:owner/:repo/ai/tests?format=raw` (raw text w/ MIME), `POST /:owner/:repo/ai/tests/generate` (requireAuth, renders highlighted source + AI-generated failing test with copy button).
478- `src/routes/discussions.tsx` (Block E2) — full discussion CRUD + categories + q-and-a answers + lock/pin. Exports `isValidCategory(c)` helper. Owner-only lock/pin; owner-or-author can close/toggle.
479- `src/routes/gists.tsx` (Block E4) — `GET /gists` discover, `/gists/new|:slug|:slug/edit|:slug/delete|:slug/star|:slug/revisions|:slug/revisions/:rev` + `/:username/gists`. Exports `generateSlug()` (8-hex) and `snapshotOf(files)` JSON serializer. Retries on slug collision up to 5x.
480- `src/routes/projects.tsx` (Block E1) — kanban board CRUD. Auto-seeds three default columns on project create. `/:owner/:repo/projects/:number/items/:itemId/move` recomputes position via `max+1` of target column.
481- `src/routes/wikis.tsx` (Block E3) — DB-backed wiki with revision history + revert. Exports `slugifyTitle(title)` (lowercase alphanumerics joined by single dashes, trimmed). Every edit appends a `wiki_revisions` row; revert creates a new revision.
a79a9edClaude482- `src/routes/merge-queue.tsx` (Block E5) — `GET /:owner/:repo/queue` list, `POST /:owner/:repo/pulls/:n/enqueue` (requireAuth), `POST /:owner/:repo/queue/:id/dequeue` (owner-or-enqueuer), `POST /:owner/:repo/queue/process-next?base=X` (owner-only, re-runs gates against base then updates base ref). PR page has an extra "Add to merge queue" button.
483- `src/lib/merge-queue.ts` (Block E5) — `enqueuePr`, `dequeueEntry`, `peekHead`, `markHeadRunning`, `completeEntry`, `isQueued`, `queueDepth`, `listQueue`, `listQueueWithPrs`. No side effects beyond the `merge_queue_entries` table; callers own gate execution + git updates.
484- `src/routes/required-checks.tsx` (Block E6) — `/:owner/:repo/gates/protection/:id/checks` CRUD (owner-only, requireAuth). "Required checks" link added on gates settings UI next to each branch protection rule.
485- `src/lib/branch-protection.ts` extends for E6 — `listRequiredChecks(branchProtectionId)`, `passingCheckNames(repositoryId, commitSha)` (scans `gate_runs` + `workflow_runs`), and `evaluateProtection(rule, ctx, requiredChecks[])` now takes a third param + reports `missingChecks`.
486- `src/routes/protected-tags.tsx` (Block E7) — `/:owner/:repo/settings/protected-tags` CRUD (owner-only, requireAuth).
487- `src/lib/protected-tags.ts` (Block E7) — `matchProtectedTag`, `isProtectedTag`, `canBypassProtectedTag`, `listProtectedTags`, `addProtectedTag`, `removeProtectedTag`, `userIdFromUsername`. Matching uses `matchGlob` from environments.ts with `refs/tags/` prefix stripped. Post-receive hook writes audit log entries (`protected_tags.{create|update|delete}_violation_candidate`) on matched pushes.
8f50ed0Claude488- `src/lib/traffic.ts` (Block F1) — `track`, `trackView`, `trackClone`, `trackByName(owner, repo, kind, meta)`, `summarise(repoId, windowDays=14)`, pure `bucketDaily(events)`. SHA-256-truncated IP hashing (16 hex) for unique-visitor approximation. All callers use `.catch(() => {})` fire-and-forget.
489- `src/routes/traffic.tsx` (Block F1) — `GET /:owner/:repo/traffic` (owner-only) with 7/14/30/90d windows, ascii-bar daily chart, top referers, unique visitors.
490- `src/routes/org-insights.tsx` (Block F2) — exports `computeOrgInsights(orgId)` returning `OrgInsightsSummary` (repoCount, gateRunsTotal, greenRate, openIssues, openPrs, mergedPrs30d, perRepo[]). `GET /orgs/:slug/insights` requires org membership. No new tables.
491- `src/lib/admin.ts` (Block F3) — `isSiteAdmin(userId)` with bootstrap rule (empty `site_admins` → oldest user wins), `listSiteAdmins`, `grantSiteAdmin`, `revokeSiteAdmin`, `getFlag`, `setFlag`, `listFlags`, `KNOWN_FLAGS = { registration_locked, site_banner_text, site_banner_level, read_only_mode }`. All helpers swallow DB errors.
492- `src/routes/admin.tsx` (Block F3) — `GET /admin` dashboard (user/repo/admin counts + recent signups), `/admin/users` + toggle grant/revoke, `/admin/repos` + nuclear delete, `/admin/flags` form. All mutations audit-logged via `audit()`. Gated through a `gate(c)` helper that returns `{user} | Response`.
493- `src/lib/billing.ts` (Block F4) — plan + quota helpers. `FALLBACK_PLANS` (free/pro/team/enterprise) mirror the seed rows. `getUserQuota(userId)` auto-initialises free row. `bumpUsage`, `checkQuota` (fail-open), `wouldExceedRepoLimit`, `resetIfCycleExpired`, `formatPrice`. Never throws into request path.
494- `src/routes/billing.tsx` (Block F4) — `GET /settings/billing` (personal view with usage bars + plan cards), `GET /admin/billing` (site-admin user/plan table), `POST /admin/billing/:userId/plan` (override plan, audit-logged).
eae38d1Claude495- `src/routes/pwa.ts` (Block G1) — `/manifest.webmanifest`, `/sw.js`, `/icon.svg`. Exports `MANIFEST`, `SERVICE_WORKER_SRC`, `PWA_REGISTER_SNIPPET` for testing. SW deliberately skips `.git/`, `/api/`, `/login*`, `/register`, `/logout`.
496- `src/lib/graphql.ts` (Block G2) — hand-rolled recursive-descent parser (`parseQuery`) + executor (`execute`) over a fixed schema. Zero dependencies. Root fields: viewer, user, repository, search, rateLimit. No mutations.
497- `src/routes/graphql.ts` (Block G2) — `POST /api/graphql` JSON endpoint + `GET /api/graphql` GraphiQL-lite explorer (Cmd+Enter to run).
498- `cli/gluecron.ts` (Block G3) — single-file Bun CLI. Exports `dispatch(argv, out)` for programmatic use, `HELP` constant, `loadConfig`/`saveConfig`. Config at `~/.gluecron/config.json` (0600). Compile: `bun build cli/gluecron.ts --compile --outfile gluecron`.
499- `vscode-extension/` (Block G4) — VS Code extension with `package.json` declaring four commands (explainFile, openOnWeb, searchSemantic, generateTests) + `gluecron.host` / `gluecron.token` settings. Detects Gluecron remotes via `git config remote.origin.url`.
06139e6Claude500- `src/lib/marketplace.ts` (Block H1+H2) — marketplace + app identity surface. `KNOWN_PERMISSIONS` (10 scopes), `KNOWN_EVENTS` (8 kinds). Pure helpers: `slugify` (40-char cap), `botUsername` (`<slug>[bot]`), `normalisePermissions` (drops unknown, de-dupes), `parsePermissions` (JSON), `hasPermission` (write→read implication), `permissionsSubset`, `generateBearerToken` (`ghi_` prefix + 24-byte hex), `hashBearer` (sha256). DB helpers: `listPublicApps(query)`, `getAppBySlug`, `createApp` (retries slug collisions, creates matching bot row), `installApp` (idempotent soft-update), `uninstallApp` (revokes all tokens), `issueInstallToken` (1h TTL default), `verifyInstallToken` (checks revoked/expired/uninstalled/suspended), `listInstallationsForApp`, `listInstallationsForTarget`, `listEventsForApp`, `countInstalls`. Never throws into request path.
501- `src/routes/marketplace.tsx` (Block H1+H2) — public marketplace + developer UX. `GET /marketplace` (directory + search), `GET /marketplace/:slug` (detail + install form), `POST /marketplace/:slug/install` (v1 user-target only), `POST /marketplace/installations/:id/uninstall` (installer-only), `GET /settings/apps` (personal list), `GET+POST /developer/apps-new` (register), `GET /developer/apps/:slug/manage` (event log + install count, owner-only), `POST /developer/apps/:slug/tokens/new` (show-once `ghi_` token). All mutations audit-logged.
08420cdClaude502- `src/routes/code-scanning.tsx` (Block I5) — `GET /:owner/:repo/security` (softAuth, private-repo visibility enforced). Aggregates last-100 scan-related `gate_runs`, builds `latestByName` map, renders summary cards + scanner status list + recent runs.
503- `src/routes/sponsors.tsx` (Block I6) — public `/sponsors/:username` + maintainer `/settings/sponsors` (requireAuth). Tier CRUD (`POST /settings/sponsors/tiers/new`, soft-retire via `is_active=false` on delete). Exports `sponsorshipTotalForUser(userId)` helper and `__internal.formatCents` for tests.
504- `src/lib/email-digest.ts` (Block I7) — `composeDigest(userId, since?)` (never throws, null on failure), `sendDigestForUser(userId)` (opt-out check + updates `last_digest_sent_at` on success), `sendDigestsToAll()` (iterates opted-in users). Pulls notifications + owned-repo gate_runs (failed/repaired) + merged PRs over last 7d. Builds text + escaped HTML body. Exports `__internal = { textToHtml, escapeHtml, fmtRange }` for tests.
505- `src/routes/admin.tsx` (extends Block F3 for I7) — adds `GET /admin/digests` (opted-in count + recently sent list), `POST /admin/digests/run` (calls `sendDigestsToAll`, audit-logged with counts), `POST /admin/digests/preview` (sends to one user by username, audit-logged). New "Email digests" tile on the /admin dashboard grid.
506- `src/routes/settings.tsx` (extends for I7) — adds `notify_email_digest_weekly` checkbox to email prefs + handler wiring in `POST /settings/notifications`, and `GET /settings/digest/preview` (renders `composeDigest` output inline via `raw(body.html)` with Hono's `hono/html`).
4c8f666Claude507- `src/lib/symbols.ts` (Block I8) — regex-based top-level symbol extractor. Pure helpers: `detectLanguage(path)` (10 extensions mapped to 8 languages), `extractSymbols(content, lang)` (per-language rule list, 1-based line numbers, 240-char signature cap, skips lines >500 chars). `indexRepositorySymbols(repoId)` walks the default-branch tree, caps at 2000 files / 1MB each, replaces the prior set in batches of 500. `findDefinitions(repoId, name)` + `countSymbolsForRepo(repoId)`. `__internal` exposes `RULES` + `EXT_LANG` for tests.
508- `src/routes/symbols.tsx` (Block I8) — `/:owner/:repo/symbols` overview (total + per-kind counts + A–Z list with blob deep-links), `/:owner/:repo/symbols/search?q=` prefix search (ilike `q%`), `/:owner/:repo/symbols/:name` detail (all definitions with signature preview + deep link). `POST /:owner/:repo/symbols/reindex` is requireAuth + owner-only.
4a0dea1Claude509- `src/lib/mirrors.ts` (Block I9) — upstream URL validator (accepts https/http/git schemes, rejects ssh/file/paths/shell metacharacters, 2048-char cap), `safeUrlForLog` (redacts embedded credentials), `upsertMirror` / `deleteMirror` / `getMirrorForRepo` / `listRecentRuns`, `runMirrorSync` (runs `git fetch --prune --tags --no-write-fetch-head` via `Bun.spawn` with 5-min timeout + `GIT_TERMINAL_PROMPT=0`; updates `last_synced_at` + `last_status` + `last_error`), `listDueMirrors` + `syncAllDue` for the admin trigger.
510- `src/routes/mirrors.tsx` (Block I9) — owner-only config at `/:owner/:repo/settings/mirror` (GET form + recent-runs panel, POST save, POST delete, POST sync-now). Site-admin `POST /admin/mirrors/sync-all` iterates due mirrors. All mutations `audit()`-logged.
9ff7128Claude511- `src/lib/rulesets.ts` (Block J6) — exports `RULE_TYPES` (6 types), pure `globToRegex`, defensive `parseParams`, pure evaluator `evaluatePush(rulesets, ctx) → {allowed, violations}`. CRUD: `listRulesetsForRepo`, `getRuleset`, `createRuleset`, `updateRulesetEnforcement`, `deleteRuleset`, `addRule`, `deleteRule`. `__internal = { evalRule, globToRegex, parseParams }` for tests. Every rule variant no-ops on malformed params or bad regex rather than throwing.
512- `src/routes/rulesets.tsx` (Block J6) — owner-only UI at `/:owner/:repo/settings/rulesets` (list + create), `/:id` (detail + enforcement toggle + add rule), `/:id/delete`, `/:id/rules/:ruleId/delete`. All mutations `audit()`-logged via the existing `gate(c)` owner pattern.
d62fb36Claude513- `src/lib/close-keywords.ts` (Block J7) — pure parser. Exports `extractClosingRefs(text)` + `extractClosingRefsMulti(sources[])`. Verbs: close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved. Case-insensitive, word-boundary-respecting, ignores cross-repo `owner/repo#N` refs, rejects non-positive numbers, returns sorted de-duped list. Tolerates `:` / `-` / whitespace between verb and ref.
0cdfd89Claude514- `src/lib/commit-statuses.ts` (Block J8) — pure helpers: `isValidSha`, `isValidState`, `sanitiseContext`, `reduceCombined`. DB helpers: `setStatus` (delete-then-insert upsert on `(repo, sha, context)`, SHA lowercased, description/url clamped to 1000/2048 chars), `listStatuses` (newest first), `combinedStatus` (latest per context, reduces to worst state, returns counts + context pills). `STATUS_STATES` exported array. Never throws on clamping; null/empty description returns null.
515- `src/routes/commit-statuses.ts` (Block J8) — `POST /api/v1/repos/:owner/:repo/statuses/:sha` (requireAuth — accepts session/OAuth/PAT; owner-only), `GET /api/v1/repos/:owner/:repo/commits/:sha/statuses` (softAuth, private-repo visibility enforced), `GET /api/v1/repos/:owner/:repo/commits/:sha/status` (combined rollup, same visibility rules).
4438e31Claude516- `src/lib/contribution-heatmap.ts` (Block J9) — pure heatmap builder. Exports `buildHeatmap(activities, windowDays=365, today?)` returning 53-week Sunday-aligned grid + rollup stats. Helpers: `levelFor(count, max)` (5-level quartile bucket), `formatDateKey` (UTC YYYY-MM-DD), `startOfUtcDay`, `daysBetween`. `__internal` re-exports for tests. Silently ignores invalid dates + activity outside the window.
3648956Claude517- `src/lib/badge.ts` (Block J10) — zero-IO SVG badge renderer. Exports `renderBadge({label, value, color, labelColor})`, `escapeXml`, `estimateTextWidth` (Verdana-11 per-char heuristic), `colorForState` (success/passed→green, pending→yellow, failure/failed/error→red, else grey). Named colour table (green/red/yellow/blue/grey/orange) + hex literals accepted. Label + value clamped to 64 chars each before rendering. Shields.io flat style with `<title>` + `aria-label` + shadow/main text pairs.
518- `src/routes/badges.ts` (Block J10) — serves `/:owner/:repo/badge/gates.svg` (latest 20 gate_runs rollup), `/issues.svg` (open count), `/prs.svg` (open count), `/status.svg` (combined commit status on default-branch HEAD), `/status/:context.svg` (single context on HEAD). Every handler wrapped in try/catch and returns a grey "unknown" SVG on DB or git failure — never 500. `image/svg+xml; charset=utf-8`, `Cache-Control: public, max-age=60, stale-while-revalidate=300`. `softAuth` so public-repo badges don't require cookies.
f9e7c01Claude519- `src/lib/community.ts` (Block J12) — pure + git-layer community-health helpers. Exports `CHECKLIST` (8 items, 3 required), pure matchers (`isReadme`, `isLicense`, `isCodeOfConduct`, `isContributing`, `isPrTemplate`), pure `checklistFromInputs({rootEntries, githubEntries, issueTemplateDirExists, description, topics})`, `buildReport` (percent + required breakdown), and `computeHealth({owner, repo, description, topics})` which walks default-branch root + `.github/`. Always returns a zero-score report on git/DB failure; never throws.
8ec29ffClaude520- `src/lib/pinned-repos.ts` (Block J13) — pinned-repo helpers. Exports `MAX_PINS=6`, pure `sanitisePinIds` (trim + de-dup + clamp to MAX_PINS), `listPinnedForUser` (position-ordered, owner-username joined), `setPinsForUser` (delete-then-insert, filters out private repos the viewer doesn't own), `listPinCandidates` (owned repos). Always returns safe defaults on DB failure.
521- `src/routes/pinned-repos.tsx` (Block J13) — serves `GET/POST /settings/pins` (requireAuth). Checkbox grid over own repos; the first MAX_PINS ticked are stored in position order. `?saved=1` flash on success.
f9e7c01Claude522- `src/routes/community.tsx` (Block J12) — serves `/:owner/:repo/community`. softAuth. Renders progress bar (green ≥80%, yellow ≥50%, else red) + per-item row with required badge + "Add <path>" or "Edit settings" CTA.
3247f79Claude523- `src/lib/review-requests.ts` (Block J11) — PR review-request lifecycle helpers. Pure: `isValidSource`, `isValidState`, `nextState` (state machine; `dismissed` terminal, `commented` is no-op), `sanitiseCandidates` (de-dup + drop author). DB: `requestReviewers` (idempotent, skips existing (pr, reviewer) rows), `listForPr` (joins `users` for username), `dismissRequest`, `recordReviewOutcome`, `autoAssignFromCodeowners` (diff paths → CODEOWNERS → user IDs → review requests + `review_requested` notifications), `countPendingForUser` (for inbox badges). Every DB helper swallows errors and returns safe defaults — never throws.
bed6b57Claude524- `src/lib/issue-dependencies.ts` (Block J14) — issue "blocker blocks blocked" dependency helpers. Pure: `wouldCreateCycle` (BFS following forward blocks edges; self-refs return true), `summariseBlockers` (counts {open, closed, total}). DB: `addDependency` (rejects with `{ok:false, reason: 'self'|'cross_repo'|'exists'|'cycle'|'not_found'|'error'}`), `removeDependency`, `listBlockersOf`, `listBlockedBy` (join issues + users for number/title/state/author). Same-repo enforcement at app layer. `__internal` re-exports for tests.
a53ceabClaude525- `src/lib/release-notes.ts` (Block J15) — pure release-notes generator. Exports `classifyCommit` (conventional prefix + scope + `!` breaking + trailing `(#N)` capture; handles `feature`/`bugfix`/`doc`/`tests` aliases; `Merge pull request #N` + `Merge branch ...` detection), `groupCommits`, `contributorsFrom`, `renderNotesMarkdown` (Breaking-changes section first, then 13 ordered buckets, then Contributors + Full-Changelog compare link). Zero-IO, never throws. `__internal` re-exports for tests.
21ff9beClaude526- `src/lib/pr-auto-merge.ts` (Block J16) — PR auto-merge opt-in helpers. `MERGE_METHODS = ['merge','squash','rebase']`, pure `isValidMergeMethod` + `computeAutoMergeAction({autoMergeEnabled, prState, isDraft, combinedState, totalChecks})` returning `{action: 'wait'|'merge'|'skip', reason}`. DB: `enableAutoMerge` (delete-then-insert), `disableAutoMerge`, `getAutoMergeForPr`, `recordEvaluation` (stamps `last_status`+`notified_ready`), `listAutoMergePrsForRepo` (open + auto-merge-enabled). `__internal` re-exports for tests.
527- `src/lib/pr-auto-merge-trigger.ts` (Block J16) — fire-and-forget trigger called from `POST /api/v1/repos/:o/:r/statuses/:sha`. Resolves each opted-in PR's head branch via `resolveRef`, matches against the incoming SHA, runs `combinedStatus` + `computeAutoMergeAction`, and on first transition to ready posts a PR comment + notifies the PR author. All catch blocks so it never breaks the status write.
9a4eb42Claude528- `src/lib/issue-templates.ts` (Block J17) — multi-template frontmatter parser + git loader. Exports `TEMPLATE_DIRS` (four standard locations), `MAX_TEMPLATE_BYTES=32KB`, `MAX_TEMPLATES=20`. Pure: `splitFrontmatter` (handles `---\n...\n---\n` with trailing whitespace on the closing fence), `parseFrontmatterMeta` (flat `key: value`, flow-list `labels: [a,"b",c]`, block-list `labels:\n - a\n - b`, single+double quote stripping, case-insensitive keys, comment/blank tolerant), `slugFromFilename` (lowercase, non-alnum collapse, 64-char clamp), `buildTemplateFromFile` (merges filename + meta + body). DB/git: `listIssueTemplates` (scans `TEMPLATE_DIRS` on default branch, filters `.md|.markdown`, excludes `config.*`, dedupes by slug, caps at `MAX_TEMPLATES`, silently returns `[]` on any failure). `findTemplateBySlug` + `__internal` re-exports.
7c975c6Claude529- `src/lib/repo-pulse.ts` (Block J18) — pure rollup builder for the Pulse page. Exports `PULSE_WINDOWS = ['1d','7d','30d','90d']`, `DEFAULT_WINDOW='7d'`, `parseWindow` (validates or falls back), `windowStart(now, w)` (subtracts days, doesn't mutate), `windowDays`. Pure: `summariseCommits` (in-window filter, email-lowercased author-grouping, count-desc/name-asc sort, tracks first/last SHA), `summarisePrs` (opened = createdAt in window, mergedCount = mergedAt in window, closed = closedAt in window but not merged, active = state=open AND updatedAt in window, returns openedList + mergedList for the UI), `summariseIssues` (same shape for issues), `buildPulseReport` one-shot builder. `__internal` re-exports for tests.
530- `src/routes/pulse.tsx` (Block J18) — serves `GET /:owner/:repo/pulse[?window=…]`. softAuth. Fetches commits via `listCommits` (500 limit), PR + issue rows via Drizzle (1000 limit each) in parallel, passes all three into `buildPulseReport`. Renders eight KPI cards (opened/merged/closed/active PRs, opened/closed/active issues, commits), top-10 contributors, recently-merged PRs, newly-opened + closed issues. `resolveRepo` wrapped in try/catch so DB failure returns 404 instead of 500. Insights page header gets a "Pulse →" link.
26484ebClaude531- `src/lib/atom-feed.ts` (Block J19) — pure Atom 1.0 feed renderer. Exports `escapeXml` (five metacharacters), `toIsoUtc` (Date + ISO with epoch fallback), `renderAtomFeed(input)` (XML-declaration-prefixed, `<feed>` with `<id>/<title>/<updated>/<link rel=self>`, per-entry `<id>/<title>/<link>/<updated>/<published>/<author>/<summary>/<content>`), `ATOM_CONTENT_TYPE` constant, `__internal` re-exports. Zero deps; `pickFeedUpdated` auto-derives from newest entry when not set.
532- `src/routes/feeds.ts` (Block J19) — serves `GET /:owner/:repo/{commits,releases,issues}.atom`. softAuth. Up to 50 entries per feed. `resolveRepo` wrapped in try/catch; private repos 404 (as empty-feed doc) for non-owner viewers. Every handler returns `application/atom+xml; charset=utf-8` with `Cache-Control: public, max-age=60, stale-while-revalidate=300`. Commits source: `listCommits` on default branch. Releases source: `releases` table minus drafts. Issues source: `issues` table newest-first.
d6b4e67Claude533- `src/lib/stale-issues.ts` (Block J20) — pure stale-issue filter + bucketer. Exports `STALE_PERIODS = ['30d','60d','90d','180d']`, `DEFAULT_STALE_PERIOD='60d'`, `periodDays`, `parsePeriod`. Pure: `filterStale(issues, now, thresholdDays)` drops non-open states + unparseable `updatedAt`, returns oldest-first with `daysSinceUpdate` and carried-through `commentCount`; `bucketByStaleness` splits into `30-60` / `60-90` / `90-180` / `180+` (inclusive at lower bound); `buildStaleReport` one-shot builder emits `{period, thresholdDays, now: ISO, total, issues, buckets}`. Accepts Date + ISO; `__internal` re-exports.
534- `src/routes/stale-issues.tsx` (Block J20) — serves `GET /:owner/:repo/issues/stale[?period=…]`. softAuth. Fetches open issues only (2000 cap) plus a grouped `count(issue_comments.id)` per issue number, calls `buildStaleReport`. `resolveRepo` wrapped in try/catch so DB failure returns 404, not 500. **Must be mounted BEFORE `issueRoutes`** so the static `/issues/stale` path wins over `/issues/:number` (which would `parseInt("stale")` into NaN). Renders four bucket cards (auto-hides buckets below the threshold), an age-coloured table (>=90d amber, >=180d red), or an empty-state card. Non-destructive: we surface staleness, never auto-close.
f3e1844Claude535- `src/lib/codeowners-lint.ts` (Block J21) — pure CODEOWNERS linter. Exports `lexCodeowners(content)` (line-aware, CRLF + inline-comment tolerant, classifies each non-blank/non-comment line as rule or malformed), `classifyOwnerToken(token, hadAt) → 'user'\|'team'\|'email'\|'invalid'` (GitHub-parity regexes: username `[A-Za-z0-9][A-Za-z0-9-]{0,38}`, team `org/team`, RFC-loose email), `isPlausiblePattern` (bracket-balance + no-whitespace), async `validateCodeowners(content, resolver)` returning `LintReport` with `findings` sorted by line then severity, plus derived `errors/warnings/infos/ok`. Never throws. `__internal` re-exports regexes + helpers.
536- `src/routes/codeowners-lint.tsx` (Block J21) — serves `GET /:owner/:repo/codeowners`. softAuth; private repos 404 for non-owner viewers. Walks the three standard paths (`CODEOWNERS`, `.github/CODEOWNERS`, `docs/CODEOWNERS`) on the default branch, builds a per-request memoising resolver over `users.username` + `organizations.slug` + `teams.slug`, calls `validateCodeowners`. Renders four summary cards (rules / errors / warnings / info), a findings list with line + severity + code + message, and the file body with line numbers. Gracefully empty-states when no CODEOWNERS file is found, with a one-click link to create `.github/CODEOWNERS` in the web editor.
7c0203eClaude537- `src/lib/code-suggestions.ts` (Block J22) — pure suggestion extractor + applier. Exports `detectLineEnding` (CRLF wins if any present), `splitLines`, `extractSuggestions(body)` (parses ``` ```suggestion ``` `` fences with CommonMark-parity rules: indented openers up to 3 spaces, 3+ backtick fences, case-sensitive language token, skips unterminated), `applySuggestionToContent({content,startLine,endLine,suggestion})` (1-indexed inclusive range; preserves original EOL + trailing-newline presence; returns `{ok,reason}` taxonomy `bad_range\|line_out_of_bounds\|empty_content\|no_change`), `applyNthSuggestion` convenience. `__internal` re-exports for tests.
538- `src/routes/code-suggestions.tsx` (Block J22) — serves `POST /:owner/:repo/pulls/:number/comments/:commentId/apply-suggestion`. `requireAuth`. Authorises applier as owner / PR author / comment author. Validates PR is open, comment has `file_path` + `line_number`, suggestion index is in range. Reads the file on the PR's head branch, applies the pure transformer, then commits via git plumbing (`hash-object` → `ls-tree -r` rewrite → `mktree` → `commit-tree -p` head → `update-ref refs/heads/<head>`). Commit message: `Apply suggestion from #N\n\nCo-authored-by: …`. Redirects to the PR with a comment anchor on success. `src/routes/pulls.tsx` PR detail page imports `extractSuggestions` and renders a "Commit suggestion" button (per-block when multiple) below the comment body for eligible viewers.
831a117Claude539- `src/lib/issue-query.ts` (Block J23) — pure GitHub-style search DSL. Exports `IssueState`/`IssueSort` types, `DEFAULT_SORT='created-desc'`, `tokenise(raw)` (whitespace-splitting with `"double"`-quoted-span support; trailing unterminated quote flushes on EOF), `parseIssueQuery(raw)` (handles `is:open\|closed`, `author:`, `label:` (repeatable, AND), `-label:` (exclude), `no:label`, `milestone:`, `sort:` allow-list; unknown qualifiers fall back to free text; never throws; tolerates null/undefined/non-string; key matching is case-insensitive). `matchIssue(issue, q)` applies each filter dimension (case-insensitive label/author/milestone; case-insensitive substring match of `q.text` against `${title}\n${body}`). `sortIssues(list, sort)` returns a new array (never mutates); unparseable dates treated as epoch 0; missing `commentCount` treated as 0. `applyQuery(raw, issues)` one-shot parse+filter+sort. `formatIssueQuery(q)` reverse-formatter with whitespace-triggered quoting. `__internal` re-exports for tests.
540- `src/routes/issues.tsx` (Block J23 integration) — issue list route accepts `?q=<DSL>`. If `q` sets `is:`, the DSL overrides the tab-based state filter. Labels joined via single `IN (…)` Drizzle query and threaded into both the DSL matcher and inline label pills. Search bar + collapsible syntax cheat-sheet + live match-count rendered above the tab pills. Tab open/closed counts remain over all issues (not filtered) so DSL use doesn't collapse them.
d6daf49Claude541- `src/lib/branch-rename.ts` (Block J24) — pure branch-rename helpers. `MAX_BRANCH_NAME_LENGTH=250`. `validateBranchName(name)` mirrors `git-check-ref-format(1)` rejecting `not_string\|empty\|too_long\|slash_boundary\|leading_dash\|dot_boundary\|consecutive_slashes\|double_dot\|at_brace\|only_at\|lock_suffix\|forbidden_char\|control_char\|empty_component\|dot_component\|lock_component`. `branchValidationMessage(reason)` returns human-readable copy for each code. `planRename({from,to,existingBranches,defaultBranch})` returns `{ok, from, to, updatesDefault}` or `{ok:false, reason, detail?}` with `same_name\|invalid_from\|invalid_to\|from_missing\|to_exists`; case-sensitive ref comparison. `shouldRewriteProtectionPattern(pattern, from)` returns true only for exact non-glob matches. `__internal` re-exports for tests.
542- `src/routes/branch-rename.tsx` (Block J24) — serves `GET/POST /:owner/:repo/settings/branches[/rename]`. Owner-only (via internal `resolveOwned` that wraps DB lookups in try/catch → null). POST path: `planRename` → on rejection redirects with the error copy from `branchValidationMessage`; on approval calls `renameBranch` (which creates `refs/heads/<to>` then deletes `refs/heads/<from>` with auto-rollback if the delete fails) + `setHeadBranch` when renaming the default. Cascades to `repositories.default_branch`, `pull_requests.base_branch`/`head_branch`, `merge_queue_entries.base_branch`, and `branch_protection.pattern` rows where `shouldRewriteProtectionPattern` returns true (duplicate-pattern conflicts swallowed silently). Every op audited as `branch.rename` with `{from, to, updatesDefault}` metadata. Audit failures never block the rename.
543- `src/git/repository.ts` (Block J24 additions) — adds `renameBranch(owner, name, from, to)` and `setHeadBranch(owner, name, branch)`. `renameBranch` resolves `refs/heads/<from>` to a SHA, writes `refs/heads/<to>` at that SHA, then deletes the old ref; on partial failure it best-effort-removes the newly-created ref so the repo never ends up with both refs pointing at the same SHA. Both helpers flush `gitCache` under `${owner}/${name}:` so branch lists + default-branch lookups see the new state.
9090df3Claude544- `src/lib/response-time.ts` (Block J25) — pure time-to-first-response helpers. `DEFAULT_WINDOW_DAYS=30`, `VALID_WINDOWS=[0,7,30,90,365]`, `parseWindow` (allow-list validator), `computeTimeToFirstResponse({issueCreatedAt, issueAuthorId, comments})` (returns earliest non-author delta in ms or null; ignores unparseable dates; clamps negative deltas to 0), `computeIssueStats(issues, windowDays, now)` (window filter is `>= now - windowDays*24h`; `windowDays=0` means all-time; drops unparseable `createdAt`), `summariseResponseTimes(stats)` (medianMs/meanMs/p90Ms via inclusive-method percentile interpolation + `unresponded` counts only open issues with null responseMs), `bucketResponseTimes(stats)` (≤1h / >1h ≤1d / >1d ≤1w / >1w; null responseMs ignored), `formatDuration(ms\|null)` (→ `ms`/`s`/`m`/`Xh Ym`/`Xd Yh`, em-dash for null, negatives clamp to `0s`), `buildResponseReport` one-shot emits `{windowDays, now, perIssue, summary, buckets, unrepliedIssueIds}` sorted oldest-first. `__internal` re-exports for tests.
545- `src/routes/response-time.tsx` (Block J25) — serves `GET /:owner/:repo/insights/response-time[?window=…]`. softAuth; private repos 404 for non-owner viewers. Fetches up to 2000 issues + all their comments via two queries (`inArray`), runs the pure report, renders eight KPI cards (total / responded / unreplied / median / mean / p90 / fastest / slowest), four bucket cards, and the top 25 oldest-unreplied open issues with a "waiting" duration. `resolveRepo` wraps DB in try/catch → never 500. Insights page header now links to it alongside Pulse.
611431dClaude546- `src/lib/audit-csv.ts` (Block J26) — pure CSV export helpers. `csvCell(value)` handles null/undefined → empty, `Date` → ISO (invalid Date → empty), RFC 4180 quoting (`,"\n\r` triggers `"..."` wrap; internal `"` doubled to `""`), and a CSV-injection guard that prefixes a cell starting with `=+-@\t\r` with a single quote (before any wrapping). `csvRow(cells)` CRLF-terminates. `csvDocument(rows)` concatenates. `AUDIT_CSV_COLUMNS = [id, when, actor, action, targetType, targetId, ip, userAgent, metadata]`. `formatAuditCsv(rows)` emits header + data rows; `normaliseCreated` accepts `Date` or ISO string and falls back to the raw string when unparseable. `auditCsvFilename(scope, now?)` emits `audit-<sanitised-scope>-<ISO>.csv` (all non-alnum in scope → `-`, empty → `audit`). `__internal` re-exports for tests.
547- `src/routes/audit.tsx` (Block J26 integration) — adds `GET /settings/audit.csv` + `GET /:owner/:repo/settings/audit.csv`, both `requireAuth`. Same filter + `LIMIT = 200` as the HTML pages. Repo variant 404s when the repo doesn't exist and 403s (`text/plain`) when the viewer isn't the owner. Response is `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="…"` and `Cache-Control: private, no-store`. HTML pages now show a "Download CSV" button next to the heading.
c02a55bClaude548- `src/lib/branch-age.ts` (Block J27) — pure branch-staleness helpers. `DAY_MS`, `VALID_THRESHOLDS = [0,30,60,90,180]`, `VALID_SORTS = [age-desc,age-asc,name,ahead-desc,behind-desc]`, `parseThreshold` + `parseSort` (allow-list validators). `computeDaysOld(tipDate, now)` returns floor-days or null for missing/unparseable (future timestamps clamp to 0). `classifyBranchAge(days)` four-bucket taxonomy (`fresh` <30d, `aging` 30–59, `stale` 60–89, `abandoned` ≥90 or null). `computeBranchRow(input, now)` folds the classification in + sets `merged = !isDefault && ahead===0`. `bucketBranches` excludes default. `filterByThreshold(rows, t)` drops default + null-daysOld when `t>0`. `summariseBranches` computes `total / nonDefault / merged / unmerged / withoutTip / oldestName / oldestDaysOld / averageAgeDays / medianAgeDays` (default excluded from everything). `sortBranchRows` is non-mutating, name-tiebreak stable, null daysOld sinks to bottom. `buildBranchReport` one-shot. `categoryLabel` / `thresholdLabel` / `sortLabel` UI copy. `__internal` re-exports.
549- `src/routes/branch-age.tsx` (Block J27) — serves `GET /:owner/:repo/branches/age`. softAuth; private repos 404 for non-owner viewers. Fetches `listBranches`, `getDefaultBranch`, and per-branch `getCommit` + `aheadBehind(defaultBranch, branch)`. Rows with git failures drop to null tipDate (category becomes `abandoned`) — never crashes. Renders six KPI cards, four bucket cards with age-colour borders, and a branches table with Ahead / Behind / Last-commit-age / Status columns. Threshold + sort selectors auto-submit. Linked from repo settings under the default-branch select.
550- `src/git/repository.ts` (Block J27 additions) — `aheadBehind(owner, name, base, head)` runs `git rev-list --left-right --count <base>...<head>`. Parses `<behind>\t<ahead>` pair. Returns null on exit failure, non-two-part output, or non-finite numbers. Non-cached — callers fan out per-branch at request time.
b184a75Claude551- `src/lib/issue-similarity.ts` (Block J28) — pure duplicate-suggestion ranker. `MIN_TOKEN_LENGTH=2`, `STOPWORDS` (~50 common English fillers), `DEFAULT_MIN_SCORE=0.15`, `DEFAULT_LIMIT=5`. `tokeniseTitle(s)` is Unicode-safe via `\p{L}\p{N}_-`, returns a deduped `Set`. `jaccard(a,b)` iterates the smaller set for efficiency; returns 0 for empty-vs-empty. `rankCandidates(title, candidates, opts)` filters by `minScore` / `state`, excludes by `excludeId` + `excludeNumber`, sorts score-desc with `createdAt`-desc + `number`-desc tie-breaks, slices to `limit`. Never mutates input. `findSimilar` is an alias. `formatSimilarityPercent(s)` clamps + rounds to whole-percent string. `__internal` re-exports.
552- `src/routes/issue-similarity.tsx` (Block J28) — serves `GET /:owner/:repo/issues/similar.json` (JSON suggestions, up to `MAX_RESULT_LIMIT=20`) and `GET /:owner/:repo/issues/:number/similar` (HTML "related issues" page). Fetches up to `CANDIDATE_LIMIT=500` issues ordered by createdAt-desc. softAuth; private repos 404 for non-owner viewers; DB errors → empty candidates → empty matches. Must be mounted before `issueRoutes` so `/issues/similar.json` doesn't get eaten by `/issues/:number`.
8c5346cClaude553- `src/lib/pr-lead-time.ts` (Block J29) — pure PR lead-time rollup. Re-exports `DEFAULT_WINDOW_DAYS`, `VALID_WINDOWS`, `parseWindow`, `formatDuration` from Block J25. `computeLeadTime({createdAt, mergedAt})` returns ms or null (null when not merged / unparseable; clamps negatives to 0). `computePrStats(prs, windowDays, now)` uses `mergedAt` as the window anchor for merged PRs so PRs with ancient `createdAt` but recent merges still appear in recent-window reports; populates `leadMs` + `inFlightMs` (only for open non-merged). `summariseLeadTimes` computes p50 / mean / p90 / fastest / slowest via inclusive-method interpolation + separate counters for `merged / openNonDraft / openDraft / closedUnmerged` so drafts don't pollute open-count KPIs. `bucketLeadTimes` uses ≤1h / ≤1d / ≤1w / >1w. `buildLeadTimeReport` one-shot, `oldestOpenIds` sorted oldest-first and excludes drafts. `__internal` re-exports.
554- `src/routes/pr-lead-time.tsx` (Block J29) — serves `GET /:owner/:repo/insights/lead-time[?window=…]`. softAuth; private repos 404 for non-owner viewers. Fetches up to 2000 PRs in a single Drizzle query, runs the pure report, renders nine KPI cards (total / merged / open-non-draft / drafts / median / mean / p90 / fastest / slowest), four bucket cards, and the top 25 oldest open PRs with an "in-flight" duration. DB failure → empty report → still 200. Linked from the Insights page header.
07efa08Claude555- `src/lib/language-stats.ts` (Block J30) — pure language-breakdown helpers. `EXTENSION_MAP` (~80 entries) maps extensions (ts/tsx/js/mjs/py/rb/go/rs/java/kt/swift/c/cpp/cs/php/scala/clj/ex/hs/lua/ml/dart/sh/ps1/sql/html/css/scss/vue/svelte/astro/md/mdx/yaml/json/toml/proto/graphql/zig/nim/…) to language names. `FILENAME_MAP` handles extensionless filenames (`dockerfile`, `makefile`, `gnumakefile`, `rakefile`, `gemfile`, `jenkinsfile`, `cmakelists.txt`, `meson.build`, `build.gradle`, `build.gradle.kts`, `pom.xml`, `package.json`, `tsconfig.json`). `LANGUAGE_COLORS` is a GitHub-ish palette; unmapped languages render with `DEFAULT_LANGUAGE_COLOR='#888888'`. `VENDORED_PREFIXES = [node_modules/, vendor/, dist/, build/, .next/, .nuxt/, coverage/, .git/, target/, bin/]` and `GENERATED_SUFFIXES` covers the full set of common lockfiles. `detectLanguage(path)` tries filename first then extension (case-insensitive; dotfiles return null since `.env`/`.gitignore` have no extension in the real-extension sense). `isVendoredOrGenerated(path)` matches both top-level and nested prefixes + lockfile basenames. `computeLanguageStats(entries, {ignoreVendored=true, maxFileSize, minLanguageBytes})` returns `{totalBytes, totalFiles, countedFiles, buckets: Array<{language, bytes, fileCount, percent, color}>, primary}` sorted bytes-desc with language-name alphabetical tie-break and "Other" always last. `foldIntoOther(report, thresholdPercent)` is idempotent + never mutates. `formatBytes` uses B/KB/MB/GB/TB with the 10+ threshold for dropping decimal places. `formatPercent(p, digits=1)` clamps to [0,100] and returns "0%" for NaN. `buildLanguageReport({entries, foldUnderPercent, …})` is the one-shot. `__internal` re-exports.
556- `src/routes/languages.tsx` (Block J30) — serves `GET /:owner/:repo/languages[?vendored=1&fold=N&ref=<branch>]`. softAuth; private repos 404 for non-owner viewers. Resolves the analyzed ref via `getDefaultBranch` unless `?ref=` is supplied (validated against whitespace / `..` / git-refspec metacharacters). Runs `listTreeRecursive` → `buildLanguageReport`, renders a stacked percentage bar + colour legend + per-language table (files / size / share). Git failures or missing default branch degrade to an empty-state panel — never 500. Include-vendored checkbox + "Fold <N%" selector auto-submit. Linked from the Insights page header.
557- `src/git/repository.ts` (Block J30 additions) — `listTreeRecursive(owner, name, ref)` runs `git ls-tree -r -l -z <ref>` (null-delimited so paths containing newlines don't break parsing), skips non-blobs (trees, submodules), skips mode `120000` symlinks so their target-length isn't counted as file size, returns `Array<{path, size}>` (`[]` on any git failure). Cached under `${owner}/${name}:tree-recursive:${ref}`.
ce08e97Claude558- `src/lib/repo-size.ts` (Block J31) — pure size-audit helpers. `DEFAULT_TOP_N=25`. `SIZE_CLASSES` is the five-tier taxonomy (tiny<1KB, small<100KB, medium<1MB, large<10MB, xlarge≥10MB) with inclusive-below boundaries + human labels. `topLevelDir(path)` extracts the first segment (returns `"."` for root files, tolerates leading `/` + non-string input). `classifyFileSize(size)` bucketizes (NaN/negative → tiny). `summariseSize(entries)` returns `{totalFiles, countedFiles, totalBytes, averageBytes, medianBytes, largestBytes, smallestBytes}` — `totalFiles` is the raw input count, `countedFiles` is the post-validation count. `bucketBySize` always returns all five buckets so the UI can render zero-count cells. `topLargestFiles(entries, {limit, minBytes})` is non-mutating, sorts bytes-desc with path-asc stable tie-break, defaults invalid limits to `DEFAULT_TOP_N`, and populates each result's `topDir`. `summariseByTopDir` groups by first segment, sorts bytes-desc with the root (`.`) bucket sinking to the bottom on ties. `buildSizeReport({entries, topN, minBytesForLargest})` one-shot. `__internal` re-exports.
559- `src/routes/repo-size.tsx` (Block J31) — serves `GET /:owner/:repo/insights/size`. softAuth; private repos 404 for non-owner viewers. Resolves ref via `getDefaultBranch` unless `?ref=` is supplied (sanitised against whitespace / `..` / git-refspec metacharacters). Calls `listTreeRecursive` → `buildSizeReport`. `top=N` capped at 200; `min=B` capped at 1 GiB; both default to sane values on garbage input. Largest-files table links each path through to `/:owner/:repo/blob/<ref>/<path>` with proper encodeURIComponent per segment. Empty trees render an empty-state panel. Linked from the Insights page header. Must be mounted before `insightsRoutes` so the static `/insights/size` path wins over any future `/insights/:id` dynamic route.
28bc555Claude560- `src/lib/pr-size.ts` (Block J32) — pure PR size-distribution rollup. `PR_SIZE_CLASSES` five-tier taxonomy (XS ≤10 / S ≤50 / M ≤250 / L ≤1000 / XL >1000 lines changed, inclusive-below boundaries). `classifyPrSize(linesChanged)` (NaN/negative → xs). `computePrSizeStats(prs, windowDays, now)` anchors window on `mergedAt` for merged PRs; drops unparseable dates; clamps negative/NaN additions+deletions to 0. `summarisePrSizes` — p50/p90/mean via inclusive-method percentile interpolation, separate merged/non-draft-open counters, `smallPrRatio` (% XS+S) rounded to 1 decimal. `bucketPrSizes` always returns all five buckets. `topLargestPrs` non-mutating, PR-number-desc tie-break, defaults bogus limits to `DEFAULT_TOP_N=10`. `buildPrSizeReport` one-shot. Re-exports `parseWindow`/`VALID_WINDOWS`/`DEFAULT_WINDOW_DAYS` from J25. `__internal` re-exports.
561- `src/routes/pr-size.tsx` (Block J32) — serves `GET /:owner/:repo/insights/pr-size[?window=…&top=N]`. softAuth; private repos 404 for non-owner viewers. Fetches up to 500 PRs, runs `diffNumstat` per PR in parallel (errors → 0 lines, still renders in XS), feeds into `buildPrSizeReport`. Eight KPI cards, five-class histogram with traffic-light borders, largest-PRs table with +a/-d breakdown + colour-coded size-class badge. topN capped at 50. Linked from the Insights page header.
562- `src/git/repository.ts` (Block J32 additions) — `diffNumstat(owner, name, base, head)` runs `git diff --numstat base..head`, sums additions/deletions/files (binary files count as 0). Returns null on git failure.
9ab6971Claude563
564### 4.7 Views (locked contracts)
565- `src/views/layout.tsx` — `Layout` accepts `title`, `user`, `notificationCount`
3cbe3d6Claude566- `src/views/components.tsx` — `RepoHeader`, `RepoNav` (active: `code|issues|pulls|commits|releases|actions|gates|insights|explain|changelog|semantic`), `RepoCard`, etc.
6fc53bdClaude567- `src/views/reactions.tsx` — `ReactionsBar` (no-JS compatible, form-per-emoji)
568- Nav links: logo · search · theme-toggle · Explore · Ask · Notifications · New · Profile (or Sign in / Register)
9ab6971Claude569- Keyboard chords: `/`, `Cmd+K`, `?`, `n`, `g d`, `g n`, `g e`, `g a`
570
571### 4.8 Tests (locked)
572- `src/__tests__/green-ecosystem.test.ts` — secret scanner, codeowners, AI fallback, health, rate-limit headers, `/shortcuts`, `/search`
573- All other existing test files — do not delete without owner permission
574
575### 4.9 Invariants (never break these)
576- `isAiAvailable()` guard returns true fallback strings when no ANTHROPIC_API_KEY. AI features degrade gracefully.
577- `getUnreadCount` never throws; returns 0 on any error.
578- Rate-limit middleware adds `X-RateLimit-Limit` + `X-RateLimit-Remaining` to every response, including 500s.
579- `c.header("X-Request-Id", ...)` set by request-context on every response.
580- Secret scanner skips binary/lock paths (`shouldSkipPath`).
581- `SECRET_PATTERNS` is an exported array. Its shape is `{ type, regex, severity }`.
6fc53bdClaude582- Theme routes live outside `/settings/*` (they must work for logged-out visitors). Cookie name: `theme`, values: `dark|light`.
583- Draft PRs cannot be merged — `/pulls/:n/merge` returns a redirect with the draft error when `pr.isDraft=true`.
584- Reactions API accepts only `ALLOWED_EMOJIS` and `ALLOWED_TARGETS`. Toggle is idempotent per (user, target, emoji).
24cf2caClaude585- `sendEmail()` never throws — always resolves to `{ ok, provider, ... }`. Email failures never break notification delivery or the primary request path.
586- Email fan-out in `notify()` is scoped to kinds in `EMAIL_ELIGIBLE` (mention / review_requested / assigned / gate_failed). Each eligible kind maps to exactly one user preference column.
587- Issue + PR template loading must return `null` on any git-subprocess failure (templates are a convenience, not a requirement). Forms always render.
9ab6971Claude588
589---
590
591## 5. OPERATIONAL NOTES
592
593### 5.1 Running locally
594```bash
595bun install
596bun dev # hot reload
28bc555Claude597bun test # 1448 tests currently pass
9ab6971Claude598bun run db:migrate
599```
600
601### 5.2 Environment
602- `DATABASE_URL` — Neon Postgres
603- `ANTHROPIC_API_KEY` — unlocks AI features
604- `GIT_REPOS_PATH` — default `./repos`
605- `PORT` — default 3000
24cf2caClaude606- `EMAIL_PROVIDER` — `log` (default, stderr-only) or `resend`
607- `EMAIL_FROM` — sender address for outbound mail
608- `RESEND_API_KEY` — required when `EMAIL_PROVIDER=resend`
609- `APP_BASE_URL` — canonical URL used to build absolute links in emails
3cbe3d6Claude610- `VOYAGE_API_KEY` — optional; when set, D1 semantic search uses Voyage `voyage-code-3` embeddings. Otherwise falls back to a deterministic 512-dim hashing embedder.
9ab6971Claude611
612### 5.3 Models
613- `claude-sonnet-4-20250514` — code review, security, chat
614- `claude-haiku-4-5-20251001` — commit messages, summaries, light tasks
615- Swap via `MODEL_SONNET` / `MODEL_HAIKU` constants in `src/lib/ai-client.ts`
616
617### 5.4 Deployment
618- `railway.toml` / `fly.toml` present
619- Crontech deploy on green push to default branch (can opt out via `autoDeployEnabled`)
620
621---
622
623## 6. SESSION WORKFLOW (WHAT THE NEXT AGENT DOES)
624
6251. Read this file, `CLAUDE.md`, `README.md`, `git log -1 --stat`.
6262. Check `git status` + current branch.
6273. Pick the next unfinished block from §3 (lowest letter + number first, unless owner specifies).
6284. Create a todo list that mirrors the sub-items of that block.
6295. Build. Write tests. Run `bun test`.
6306. Commit with `feat(<BLOCK-ID>): ...`.
6317. Push.
6328. Update this file:
633 - Move the block's row in §2 to ✅ where applicable.
634 - Add the block's files to §4 LOCKED BLOCKS.
635 - Commit + push again.
6369. Start the next block. **Do not stop to ask.**
637
638If a block is too large for a single session, split it into a sub-plan at the top of the session, ship what you can, and document what's left at the end of this file under a `## 7. IN-FLIGHT` section.
639
640---
641
642## 7. IN-FLIGHT
643
644(Intentionally empty. Add here if a block is partially complete at session end.)