mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-17 07:26:27 +00:00
8.0 KiB
8.0 KiB
Repository Guidelines
Project Overview
LeetCode interview-prep campaign (8 weeks, 2026-08-17 → Oct 11) run as a repo: solution files under work/, a Blume docs site under docs/, GitHub Actions that reconcile issues and publish docs, and a Cloudflare Worker (api/) that runs the spaced-repetition system (SRS) — daily digest email, one-tap logging, weekly review issues, live charts. Issues model the curriculum: ~24 topic issues own ~161 problem sub-issues (set:core|optional|deferred, diff:*); milestones are phases; a user-level GitHub Project ("Interview Prep", #2) mirrors review state.
Architecture & Data Flow
Three owners, one direction of truth:
flowchart LR
work[work/ solutions] -->|bun run sync| docs[docs/solutions/*.mdx]
work -->|close-solved.yml| PI[problem issues]
PI -->|close-topics.yml| TI[topic issues]
GH[GitHub issues = catalog] -->|nightly reconcile| D1[(D1 = SRS state)]
D1 -->|8 AM ET cron| Mail[digest email + one-tap links]
Mail -->|GET /log| D1
PI -->|/done webhook| D1
D1 -->|best-effort mirror| Proj[GitHub Project fields]
D1 -->|Sat midnight ET| Review[Review — Week N issue]
D1 --> Charts[SVG charts + /api/stats]
- Reconcile, don't react.
close-solved.ts,close-topics.ts, and the Worker's catalog sync recompute desired state from scratch each run: re-runs are no-ops, backfills need no special casing, closing is one-directional. - D1 owns SRS state; GitHub issues own the catalog;
api/data/schedule.jsonowns the calendar. The catalog reconcile never invents rows and never overwrites SRS columns (stage,next_review). Project fields (Target Date,SRS Stage,First Attempt) are a best-effort mirror (api/src/mirror.ts): failures are warnings, never lost D1 writes; topic rows'Target Dateis never written. - Determinism = idempotency. Drill/gate sampling uses a seeded PRNG (
rng()inapi/src/srs.ts, FNV-1a → mulberry32, seed = date / ISO week); the digest is keyed by ET date inemail_log; one-tap links are unique on (problem, date, kind). - DST-proof crons: each event has two UTC crons; code fires only on the computed ET hour (
etHour), unit-checked inapi/src/srs.test.ts. - Chained workflows:
GITHUB_TOKEN-driven issue closes fire noissuesevents, soclose-topics.ymlchains offworkflow_runof Close Solved instead. The Worker's webhook uses its ownGH_PAT, so its events flow normally.
Key Directories
| Path | Purpose |
|---|---|
work/<Difficulty>/<Category>/<num>.<slug>.{js,py} |
Solutions, e.g. work/Easy/Array/1.two-sum.py. Machine-parsed header comment (title / Difficulty: / URL / ─ rule / statement) — preserve its exact shape |
scripts/ |
Flat Bun TS: automation entries (shebang + top-level await) and libraries (no shebang, side-effect-free on import) |
docs/ |
Blume site. docs/solutions/(<category>)/<num>-<slug>.mdx generated by sync; (algorithms)/, (data-structures)/ hand-written explainers |
api/ |
Cloudflare Worker (own Bun workspace): src/ modules, data/schedule.json (day → topic issue, human-edited, bundled at deploy), migrations/, scripts/import-srs.ts |
.github/workflows/ |
deploy (docs), close-solved, close-topics — Worker deploys are manual (bun run api:deploy) |
Development Commands
bun run pick # scaffold a solution via leetcode-cli (fuzzy picker)
bun run test # run ONE solution against LeetCode's judge (not a test suite)
bun run sync # work/ → docs/solutions/ pages
bun run dev|build # Blume docs site
bun run close-solved -- --dry-run # issue reconcilers (also DRY_RUN=1)
bun run close-topics -- --dry-run
bun run api:dev # wrangler dev on :8787 (local D1); api:test = DST guard tests
bun run api:deploy # deploy the Worker (manual by design)
curl -X POST -H "Authorization: Bearer $LINK_KEY" \
"localhost:8787/admin/digest?dry=1&date=2026-08-30" # preview a digest, send nothing
Code Conventions & Common Patterns
- Scripts are either entries or libraries. Entries (
scripts/close-*.ts) use shebang + top-levelawait. Libraries (scripts/github.ts,scripts/report.ts, everything inapi/src/) are side-effect-free on import — nothing touches network/credentials until a factory (github(),projectMirror()) is called. - Shared plumbing:
github()givesrepo,api()(throwsMETHOD path -> status body),list()(paginating async generator),closeIssue()(comment first, then close — a failed PATCH still leaves a trace).report.tsgivesprintTable/markdownTable/writeStepSummary. - Narrow API payloads with guards, not inline casts:
if (!("number" in value) || typeof value.number !== "number") return;(seereadProblemIssueinclose-solved.ts). Named-const casts only with a reason. - Dry-run everything: reconcilers take
--dry-run/DRY_RUN=1; Worker admin routes take?dry=1&force=1&date=(theSRS_TODAYequivalent) andwrangler devruns against local D1 — exercise logic there before touching production state. - Dates: always ET calendar strings (
YYYY-MM-DD), arithmetic anchored at noon UTC (atNooninapi/src/srs.ts) to dodge DST. Nevernew Date()math directly. - Style: section-divider comments (
// ── name ───), file-top doc comments explaining the why and invariants, 2-space JSON with trailing newline,Mapfor dynamic keys /Recordfor static tables, no tiny one-expression wrapper functions. - Known intentional duplication:
close-solved.tsre-implements header stripping instead of importing fromsync.tsbecausesync.tsruns its pipeline on import. Don't "deduplicate" it.
Important Files
api/src/srs.ts— SRS domain: ladder (new → +2 → +5 → +10 → retired; fail resets to+2; first-ever log enters at+2), ET date math, seeded sampling,logAttempt()(the ONE write path — email taps, webhook, gate scoring all converge here).api/src/index.ts— router + cron dispatch;api/wrangler.jsonc— bindings (DB,EMAIL), crons, vars; secretsGH_PAT/WEBHOOK_SECRET/LINK_KEYviawrangler secret put.scripts/sync.ts— work→docs contract: only## Solutiononward is script-owned on existing pages; human prose is never touched; never hand-write solution pages or edit inside## Solution.README.mdlive charts are Worker endpoints (/chart/*.svg,/badge/gate.svg, 5-min Camo cache); the docs/progresspage fetches/api/statsclient-side (islands/ProgressDashboard.tsx).blume.config.ts— site base/leetcode;README.mdcampaign table doubles as topic-map input tosync.ts(readmeTopics(),TOPIC_ALIASES).- Old
.github/srs/JSON state and thesrs-*Actions are RETIRED — do not resurrect;api/scripts/import-srs.tsdocuments the migration.
Runtime/Tooling Preferences
- Bun only: run scripts with
bun scripts/<name>.ts, install withbun install --frozen-lockfile;api/is its own workspace with its own lockfile. Node 22 appears only indeploy.ymlbecause Blume requires it. - No root tsconfig, no ESLint, no Prettier — match surrounding style by hand.
api/has a stricttsconfig.json;Envtypes are generated (bunx wrangler types), never hand-written. - Zero runtime npm dependencies in automation and the Worker — SVG, HMAC (WebCrypto), GraphQL are hand-rolled; Actions scripts use Bun builtins +
fetchonly. - Local GitHub auth falls back to
gh auth token; scripts run fine outside Actions.
Testing & QA
- Repo-wide: no test framework —
bun run testsubmits one solution to LeetCode's judge. Exception:api/src/srs.test.ts(bun:test) proves the DST cron guards with fixed dates; run viabun run api:test. - QA is dry-runs + local state: Worker logic against
wrangler dev+ local D1 with?dry=1&date=; reconcilers with--dry-run; idempotency check = run twice,cmpoutput. - Docs changes:
bunx blume build --isolatedmust pass with no new warnings (thenrm -rf .blume-verify); after sync, review only created pages (prose transforms are heuristic).