From f1b61fdc41472807c1d6b419bca9af76b27e9797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Sun, 28 Jun 2026 09:49:48 +0200 Subject: [PATCH] feat(lesson-ref): accept lesson 0 for the m0l0 General Toolkit Allow lesson numbers >= 0 so general, non-lesson-bound artifacts can be fetched via m0l0 (e.g. `10x get m0l0`). Update the parser doc comment and tests; lesson 0 is now valid, negatives still rejected by the regex. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/lesson-ref.ts | 11 ++++++----- tests/lesson-ref.test.ts | 12 ++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/lesson-ref.ts b/src/lib/lesson-ref.ts index ea7bb09..40c5240 100644 --- a/src/lib/lesson-ref.ts +++ b/src/lib/lesson-ref.ts @@ -4,11 +4,12 @@ * Grammar: `ml` — lowercase `m`, one-or-more digits, lowercase `l`, * one-or-more digits. 10xdevs3 ships with modules 0..5 (module 0 is the * prework), so the module number is range-checked here; lesson numbers - * must be positive (≥ 1) but are not capped (the API is the source of - * truth on per-module lesson counts). + * must be non-negative (≥ 0) but are not capped (the API is the source of + * truth on per-module lesson counts). Lesson 0 is reserved for general, + * non-lesson-bound artifacts (e.g. m0l0 — the General Toolkit). * - * Accepted: m0l1, m1l1, m1l2, m5l3 - * Rejected: M1L1, m1, 1-1, m1l0, m6l1, "", whitespace, trailing garbage + * Accepted: m0l0, m0l1, m1l1, m1l2, m5l3 + * Rejected: M1L1, m1, 1-1, m6l1, "", whitespace, trailing garbage * * The CLI exposes a single entry — `parseLessonRef` — used by `10x get` * to validate user input before hitting the API. @@ -37,7 +38,7 @@ export function parseLessonRef(raw: string): ParsedLessonRef | null { const lesson = Number.parseInt(match[2]!, 10); if (!Number.isFinite(module) || !Number.isFinite(lesson)) return null; if (module < MIN_MODULE || module > MAX_MODULE) return null; - if (lesson < 1) return null; + if (lesson < 0) return null; return { lessonId: `m${module}l${lesson}`, module, lesson }; } diff --git a/tests/lesson-ref.test.ts b/tests/lesson-ref.test.ts index ef8909e..f9cc362 100644 --- a/tests/lesson-ref.test.ts +++ b/tests/lesson-ref.test.ts @@ -48,6 +48,14 @@ describe("parseLessonRef — valid input", () => { }); }); + it("parses general toolkit m0l0 (lesson 0)", () => { + expect(parseLessonRef("m0l0")).toEqual({ + lessonId: "m0l0", + module: 0, + lesson: 0, + }); + }); + it("exports the module-range constants used by the parser", () => { expect(MIN_MODULE).toBe(0); expect(MAX_MODULE).toBe(5); @@ -83,10 +91,6 @@ describe("parseLessonRef — rejected input", () => { expect(parseLessonRef("m-1l1")).toBeNull(); }); - it("rejects lesson zero (m1l0)", () => { - expect(parseLessonRef("m1l0")).toBeNull(); - }); - it("rejects out-of-range module m6l1 (course has modules 1..5)", () => { expect(parseLessonRef("m6l1")).toBeNull(); });