diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..54c35ae --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,80 @@ +# 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/`, and GitHub Actions automation that reconciles issues, publishes docs, and drives a spaced-repetition system (SRS). 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 + +Two subsystems, one direction of truth: + +```mermaid +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] + SRS[.github/srs/srs.json] -->|srs-scheduler| Today[πŸ“‹ Today issue] + Today -->|/done comments| Logger[srs-logger] --> SRS + SRS -->|mirror, best-effort| Proj[GitHub Project fields] + SRS -->|gate.yml| Gate[weekly gate issue + docs/gate-log.md] +``` + +- **Reconcile, don't react.** `close-solved.ts`, `close-topics.ts`, and the SRS scripts recompute desired state from scratch each run: re-runs are no-ops, backfills need no special casing, closing is one-directional (nothing ever reopens). +- **`.github/srs/srs.json` is the single source of truth.** Project fields (`Target Date`, `SRS Stage`, `First Attempt`) are a best-effort mirror via `scripts/srs-project.ts`: every GraphQL failure becomes a warning, never an error β€” an srs.json update must never be lost to a mirror failure. Topic rows' `Target Date` is never written (problem rows only). +- **Determinism = idempotency.** Drill/gate sampling uses a seeded PRNG (`rng()` in `srs.ts`, FNV-1a β†’ mulberry32, seed = date / ISO week), so running a workflow twice picks identical problems. +- **Chained workflows:** `GITHUB_TOKEN`-driven issue closes fire no `issues` events, so `close-topics.yml` chains off `workflow_run` of Close Solved instead. + +## Key Directories + +| Path | Purpose | +|---|---| +| `work///..{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/()/-.mdx` generated by sync; `(algorithms)/`, `(data-structures)/` hand-written explainers | +| `.github/srs/` | Machine state: `srs.json` (bot-committed), `schedule.json` (day β†’ topic issue, human-edited), `badge.json` (shields endpoint) | +| `.github/workflows/` | deploy, close-solved, close-topics, srs-scheduler, srs-logger, gate, srs-setup | + +## Development Commands + +```sh +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 +SRS_DRY=1 SRS_TODAY=2026-08-30 bun scripts/srs-scheduler.ts # preview a brief +bun scripts/srs-logger.ts --issue N --author prdlk --body "/done 704 pass" +bun scripts/srs-gate.ts create|close --issue N +``` + +## Code Conventions & Common Patterns + +- **Scripts are either entries or libraries.** Entries (`close-*.ts`, `srs-scheduler|logger|gate|setup.ts`) use shebang + top-level `await` and run their pipeline linearly. Libraries (`github.ts`, `report.ts`, `srs.ts`, `srs-project.ts`) must stay side-effect-free on import β€” nothing touches network/credentials until a factory (`github()`, `projectMirror()`) is awaited. +- **Shared plumbing:** `github()` gives `repo`, `api()` (throws `METHOD path -> status body`), `list()` (paginating async generator), `closeIssue()` (comment **first**, then close β€” a failed PATCH still leaves a trace). `report.ts` gives `printTable`/`markdownTable`/`writeStepSummary`. +- **Narrow API payloads with guards**, not inline casts: `if (!("number" in value) || typeof value.number !== "number") return;` (see `readProblemIssue` in `close-solved.ts`). Named-const casts only with a reason. +- **Dry-run everything:** reconcilers take `--dry-run`/`DRY_RUN=1`; SRS takes `SRS_DRY=1` plus overrides `SRS_TODAY`, `SRS_STATE`, `SRS_SCHEDULE`, `SRS_GATE_LOG`, `SRS_BADGE` β€” point `SRS_STATE` at a scratch copy to exercise logic safely. +- **Dates:** always ET calendar strings (`YYYY-MM-DD`), arithmetic anchored at noon UTC (`atNoon` in `srs.ts`) to dodge DST. Never `new Date()` math directly. +- **Style:** section-divider comments (`// ── name ───`), file-top doc comments explaining the *why* and invariants, 2-space JSON with trailing newline, `Map` for dynamic keys / `Record` for static tables, no tiny one-expression wrapper functions. +- **Known intentional duplication:** `close-solved.ts` re-implements header stripping instead of importing from `sync.ts` because `sync.ts` runs its pipeline on import. Don't "deduplicate" it. + +## Important Files + +- `scripts/srs.ts` β€” SRS domain: ladder (`new β†’ +2 β†’ +5 β†’ +10 β†’ retired`; fail resets to `+2`), `fetchCatalog()` (walks topic sub-issues, parses `LC Β· Β· Β· ` titles), campaign math (`CAMPAIGN_START`). +- `scripts/sync.ts` β€” workβ†’docs contract: only `## Solution` onward is script-owned on existing pages; human prose is never touched; never hand-write solution pages or edit inside `## Solution`. +- `.github/srs/srs.json`, `docs/gate-log.md`, `.github/srs/badge.json` β€” **bot-committed** (`github-actions[bot]`, `[skip ci]`). Don't hand-edit; fix via scripts. +- `blume.config.ts` β€” site base `/leetcode`; `README.md` campaign table doubles as topic-map input to `sync.ts` (`readmeTopics()`, `TOPIC_ALIASES`). +- Workflows share concurrency group `srs-state` (never race on srs.json); `PROJECT_PAT` secret is required for user-Project GraphQL (default `GITHUB_TOKEN` cannot), used by srs-scheduler/logger/setup only. + +## Runtime/Tooling Preferences + +- **Bun only**: run scripts with `bun scripts/.ts`, install with `bun install --frozen-lockfile`; `bun.lock` is the sole lockfile. Node 22 appears only in `deploy.yml` because Blume requires it. +- **No tsconfig, no ESLint, no Prettier** β€” match surrounding style by hand. `@types/bun` is the only type dependency. +- Automation workflows skip `bun install` (scripts use Bun builtins + `fetch` only) β€” keep new automation dependency-free. +- Local GitHub auth falls back to `gh auth token`; scripts run fine outside Actions. + +## Testing & QA + +- **No test framework, no `*.test.*` files** β€” `bun run test` submits one solution to LeetCode's judge. +- QA is dry-runs + scratch state: verify schedulers/reconcilers with `--dry-run`/`SRS_DRY=1` and a copied `SRS_STATE` before live runs; idempotency check = run twice, `cmp` output. +- Docs changes: `bunx blume build --isolated` must pass with no new warnings (then `rm -rf .blume-verify`); after sync, review only *created* pages (prose transforms are heuristic).