Skip to content

CLM v1.11.0

Choose a tag to compare

@github-actions github-actions released this 10 Jun 00:20
· 190 commits to master since this release
712d8db

Added

  • Multiple release streams per cohort (issue #291). A course can declare
    several <release-channels> blocks — one per release stream, each fed by
    its own source-target (e.g. materials from a shared target released
    before each session, solutions from a completed target released after
    the workshop). With more than one block each needs a unique name; channels
    are addressed as STREAM/CHANNEL (--channel materials/2026-04) across
    clm release, clm git --channel/--all-channels, and clm calendar, with
    bare names still accepted when unique. Derived channel repo names gain the
    stream segment ({slug}-{channel}-{stream}); spec validation rejects
    duplicate stream/channel names and channels sharing a destination or ledger
    across streams.
  • Language-scoped release channels (issue #293). <channel lang="de">
    promotes only that language's files, re-rooted so the cohort repo's root is
    the language directory — matching per-language distribution repos like
    …/machine-learning-azav-de — and appends -{lang} to the derived repo
    name. clm release sync --language de|en overrides per invocation. A
    channel without lang keeps receiving every built language root (now
    documented as the defined default).
  • Declarative GitLab access-group shares (issue #294). <share-with access="reporter">students/azav-ml/ml-2026-04</share-with> on a <channel>
    (or on the block, inherited by every channel) declares which GitLab groups a
    channel repo is shared into; the new clm release provision applies the
    shares via the GitLab API (idempotent, token from
    CLM_GITLAB_TOKEN/GITLAB_TOKEN, safe no-op for non-GitLab remotes,
    --dry-run preview). Repo creation itself stays push-to-create/manual.
  • clm git skips release build-source targets (issue #292). Without
    --target, clm git init/status/commit/push/sync/reset no longer creates
    or manages repos for output targets that only feed a release stream (named
    as a <release-channels source-target>), nor for targets with an explicit
    distribute="false" — those are private build inputs whose content reaches
    students only through clm release sync. An explicit --target NAME still
    selects such a target, and distribute="true" restores wholesale
    distribution for a release source that is also pushed directly.
  • A failing deck no longer blocks all releases (issue #295). A
    whole-course build with topic-attributable errors now writes the provenance
    manifest for the cleanly-built subset, excluding the failed topics' entries
    and recording them (partial: true, failed_topics). clm release sync
    promotes every green topic and refuses the failed ones (skip-failed,
    never frozen, loud warning) until a build succeeds for them. Errors that
    cannot be attributed to a topic — and timed-out, --watch, or
    --only-sections builds — still suppress the manifest entirely.
  • Cohort viewing calendars (clm export calendar, clm calendar check /
    status)
    project a course's schedule onto one cohort's real calendar dates
    (issue #283). Where clm export schedule is course-relative ("Week 3,
    Tuesday"), a calendar maps the same ordered day-buckets onto actual dates
    for a cohort, absorbing that cohort's holidays, delayed start, multi-week
    breaks, and catch-up days. The trainer maintains only the deltas in a small
    hand-edited release/<channel>.calendar.toml (start/end, weekly teaching
    pattern, single-or-interval holidays, and ordered merge/split/insert/
    pin adjustments) beside the channel's release ledger; the per-video dates
    are computed. A holiday removes a teaching date so later content slides
    automatically; pins anchor a day to a date and segment the timeline, and an
    over-full segment is reported with the exact "merge ≥ N buckets" deficit
    rather than silently redistributed. clm export calendar renders Markdown,
    CSV, or a subscribable .ics feed (stable event UIDs, so a re-export updates
    events in place); clm calendar check validates a calendar (date-free,
    non-zero exit on errors); clm calendar status [--as-of DATE] shows where a
    cohort is today, its plan coordinate, and the drift in days versus the ideal
    plan. See clm info commands.
  • clm export outline --weekdays [never|always] controls whether a
    section's <subsection> weekday/name groupings are rendered as bold labels
    in the Markdown outline. The default is never: every section's decks are
    flattened into plain bullets, so weeks read uniformly whether or not they
    declare subsections (previously, weeks that declared subsections rendered
    weekday groups while weeks that did not — and disabled weeks under
    --include-disabled=merge, which ignored subsections entirely — rendered
    flat, an inconsistent mix). Pass --weekdays always to group decks under
    their weekday/name label in every week, disabled weeks included. The JSON
    format is unaffected — it always carries the grouping as a structured
    subsections array.
  • Two new clm slides sync test oracles close the architecture review's
    remaining coverage gaps (issue #289 P4): tests/slides/test_sync_non_python.py
    drives the engine end-to-end on a C# (//-token) split pair for the first
    time — neutral verbatim copy, id-less re-translation, add-with-insert (the
    built twin must carry the // %% header family), tag mirroring, and the
    neutral tag-drift alert, on both baselines — and
    tests/slides/test_sync_corpus_mutation.py (slow/integration, corpus-gated
    like the no-op backstop) is the corpus' first positive propagation
    oracle
    : scripted one-sided mutations of real PythonCourses decks per
    change-type (neutral edit/add, id-less localized edit, companion remove,
    tag-only retag, judge-reconciled edit), asserting each is propagated to the
    other half or alerted — never silently dropped — on pairs selected per
    target cell class and verified post-sync-clean.

Changed

  • The course-document commands outline, schedule, and summarize moved
    under a new clm export group
    (clm export outline / clm export schedule
    / clm export summary), and the flat top-level forms were removed (no
    deprecation alias). summarize was renamed to the noun summary, with
    clm export summarize kept as an alias. The three commands now share a
    consistent option vocabulary: --include-optional and --include-disabled
    are available on all three (both off by default, so an outline/summary
    that previously listed optional="true" modules now hides them unless the
    flag is given), clm export schedule gained -d/--output-dir, and
    -L/--language is the canonical spelling everywhere (schedule keeps
    --lang as an alias). The MCP course_outline tool is unchanged (it still
    shows optional content). See clm info migration.
  • --include-disabled now takes an optional value on all three clm export
    commands: a bare --include-disabled (or =marked) keeps the previous
    behaviour (disabled content tagged (disabled), disabled whole sections
    listed after the enabled ones in outline/summary), while
    --include-disabled=merge folds disabled content into the normal course flow
    — in declared order, with no marker — so a roadmap spec reads like a finished
    course. Structured outputs (outline --format json, schedule --format csv)
    keep the disabled state recorded even under =merge.
  • clm export outline / clm export summary now filter split-companion decks
    to the requested language.
    When a topic ships split slides_x.de.py +
    slides_x.en.py companions, a -L de outline/summary listed both the
    German and the English title (and the JSON slides array carried both),
    because the section-flat, JSON-slide, summary, and disabled-topic enumerations
    skipped the output_language_filter the subsection path already applied. All
    enumerations now filter split companions to the requested language (via
    output_language_filter for the built course and the .de/.en filename
    suffix for disabled topics read from disk), so a split pair contributes a
    single entry — matching the build's per-language routing. Bilingual decks are
    unaffected.
  • clm slides sync parity errors now name the diverging cells, instead of a
    generic "a change to a shared cell was not propagated". The shared-cell error
    lists the cell text present on one half but missing on the other (or the first
    out-of-order cell), and the id-less-localized error points at the slide group
    and cell kind — so the divergence can be located without a manual diff.
  • clm slides sync shows progress while it runs. A directory (batch) sweep
    prints a [i/N] <deck> … header per pair, and a writing run prints a short
    stderr tick per LLM call (· reconciling … / · translating …) so a long
    sync is visibly alive. Progress goes to stderr and is suppressed under
    --json (stdout stays pure JSON).
  • The mitmproxy transport now records and replays a per-request response
    sequence
    instead of collapsing a repeated request to its first response.
    A non-deterministic endpoint (a temperature>0 LLM, or OpenRouter routing the
    same request to different providers) answers an identical request differently
    on successive calls; when a later request embeds an earlier response (e.g.
    summarize | translate, where translate carries the generated summary), the
    old first-seen-wins dedup dropped every response after the first, so the
    downstream request matched nothing on replay and failed with clm_replay_miss.
    Recording now keys dedup on (request, response) so distinct responses to the
    same request are kept in order, and replay serves them in recorded order via a
    per-request cursor — sticking on the last match once exhausted, so a genuinely
    repeatable request never misses and a single-entry cassette still serves
    repeatably (unchanged). The host-side fold gained preserve_sequence=True
    (mitmproxy only; the vcrpy path keeps the deduped fold). Decks affected before
    this fix: chains_and_lcel/slides_020, prompt_templates/slides_010,
    langgraph_intro/slides_010.

Fixed

  • clm slides sync now mirrors a tag-only edit on an id-less localized cell
    across a concurrent slide-group reorder
    (issue #285). Live positional
    pairing is unsound under a reorder, so the Tier C retag mirror used to
    decline — silently before #289, with an error after #290. The baseline
    provides a sound, reorder-invariant join instead: the drifted cell is found
    by unique body hash against its own baseline (a tag-only edit never changes
    the body), its baseline position indexes the twin (the two localized
    streams are positional twins at the last sync, verified on the recorded
    rows), and the twin's baseline hash locates it in the current, reordered
    stream. The tag mirrors and the reorder applies in the same clean pass.
    Anything the route cannot anchor still errors rather than guessing or
    dropping: byte-identical duplicate bodies (detected via per-group tag
    multisets — previously these slipped silently to the validator even in the
    alert path), tags drifted on both twins, misaligned baseline streams, or a
    pass that also adds/removes cells (a remove would shift the positions the
    retag applier targets — sync in two steps).
  • clm slides sync's structural pass no longer calls the translator inside
    its region rebuild
    (issue #289 P2, completing the resolve-then-apply
    redesign's last deferred follow-up). The translations a rebuild needs are
    pre-resolved into the run's shared outcome cache (the same cache the add
    walks use, so a deferred add's outcome is already there when the rebuild
    reaches for the cell); the rebuild itself is mechanical, with the documented
    inline fallback for a cache miss. Unchanged id-less cells stay on the
    verbatim-reuse path (never translated). Behavior-preserving — the full
    slides suite is green unchanged. The opt-in --llm-recover tier remains
    inline by decision (it fires only when the deterministic id-migration is
    stuck, which cannot be known before execute without simulating that tier;
    it is already cached, validated, and safe-aborting) — recorded in
    docs/claude/design/sync-plan-resolve-apply.md.
  • clm slides sync's baseline plumbing is unified, and the deterministic
    id-migration now also runs on a committed (git-HEAD) baseline
    (issue #289
    P1). Both baseline sources now produce one representation (BaselineBundle,
    the membership-widened watermark shape — git HEAD is re-derived through the
    exact chokepoints a watermark recording uses) consumed by a single code path
    for every baseline aspect: the keyed diff, the neutral / id-less / header
    drift detectors, tag mirroring, and the apply engine's anchor-reuse and
    id-migration passes (which now read the same rows the classifier diffed
    against, carried on the plan). This deletes the per-aspect parallel git-HEAD
    derivations whose coverage gaps produced the #269 silent drops and the #289
    git-HEAD tag drop — the two sources can no longer diverge in coverage by
    construction. User-visible improvement: the def-my-fun drifted-id
    migration (and the opt-in --llm-recover tier) previously read only the
    watermark, so on the first sync of a committed pair a split id'd cell was
    not re-united with its construct; it now migrates identically on both
    baselines.
  • clm slides sync no longer silently drops a one-sided tag-only edit
    (issue #289, found by the sync architecture review in
    docs/claude/sync-engine-architecture-assessment.md). Three tag-channel
    gaps — all of which previously reported "decks already consistent" and
    advanced the watermark over the divergence, permanently hiding it from
    later syncs — are closed:
    • a tag-only edit on an id-less localized cell is now mirrored on a
      committed (git-HEAD) baseline too, not only against a watermark: the
      Tier C retag classifier re-derives the baseline rows + tag sets from the
      committed text, so the first sync of a freshly-split committed pair
      carries a keep/alt tag change across;
    • a tag-only edit on a language-neutral (shared) cell — whose tags must
      match across the halves, since a neutral cell is shared verbatim header
      included — now errors (the body-hash detectors are blind to it, and
      sync has no neutral-retag mirror yet): the watermark holds and nothing is
      written. A combined body+tag edit still propagates both via the structural
      rebuild's verbatim header copy, with no false alert. The post-apply
      shared-cell parity fail-safe also compares tag sets now;
    • a tag-only edit on an id-less localized cell while the other half
      reorders slide groups
      (issue #285) now errors instead of vanishing:
      positional tag mirroring is unsound across a reorder, so the drift is
      detected order-blind (hash-keyed against each half's own baseline) and the
      watermark holds.
      A new channel-coverage meta-test pins the class: every channel the sync
      watermark records (body hash, tags, header, order, identity, construct — per
      partition) must name a live detector or fail-safe, so a future recorded
      channel can no longer ship unconsumed the way shared-partition tags did.
  • clm slides sync no longer errors when a new slide group is inserted next
    to a language-neutral cell.
    Adding a new id'd slide (a localized markdown
    cell + its following language-neutral code cells) right after a neutral cell —
    e.g. a slide_id-carrying code cell with no lang= — made sync place the new
    group in the wrong inter-group position on the other half and then fail with
    language-neutral (shared) cells differ … / id-less localized cells … placed differently …, writing nothing. The id-carrying add path anchors a new group
    only beside cells it can name by (slide_id, role), so a neutral / id-less
    neighbour was skipped; the structural pass rebuilds each group's contents but
    never reordered groups, so the misplacement survived as a parity error. Sync
    now reconciles slide-group order against the propagation source after the
    structural pass (committing only a reorder that reproduces the source's group
    and (slide_id, role) order exactly), so such an insertion propagates cleanly.
  • clm slides sync no longer silently drops (or destructively overwrites) a
    one-sided edit made while the other half reorders slide groups.
    When one half
    reordered slide groups (a move) while the other half independently edited a
    language-neutral (no lang=) or id-less-localized cell, the two changes flowed
    in opposite directions. The reorder permutes the source half's neutral/id-less
    cell sequence, which the positional drift detectors misread as a "drift" —
    masking the target half's edit. The run reported the decks consistent and
    advanced the watermark while the edit was lost from both halves; for two or
    more reordered neutral cells the positional shift was even misclassified as a
    same-cell divergence and auto-healed, overwriting the edit on disk (Issue
    #282). Because a group reorder makes positional pairing unsound, sync now
    errors whenever one half reorders slide groups while the other half has any
    unreconciled neutral / id-less change (an edit, add, remove, or cross-group
    reassignment), holding the watermark and leaving both halves untouched on disk so
    the change is preserved. A neutral edit applied identically to both halves
    alongside a reorder still merges cleanly (the halves agree, so nothing is lost).
    Reconcile a genuine conflict by hand (apply the reorder and the edit on the same
    half, or sync them in separate steps) and re-run.