Cloud agent bc-fd13dd23-…2ccd ran ~22 hours (2026-09-07 → 2026-09-08),
opened PRs #73–#149, and spawned recursive “hunt remaining bugs”
children. That loop is forbidden. Details:
.cursor/rules/no-factory-loop.mdc.
- Finish the user's list in this run. Do not stop after the first item to ask for a merge, rebase, review, or permission to continue.
- Do not ask the user to merge. Keep working on the same branch and the same PR. They merge when they want.
- One request → one branch → one PR unless they explicitly asked for separate PRs. Update that PR each turn; do not open a new PR per file or per YAML key.
- Batch isomorphic holes (typed config keys,
or defaultcoerces, the same fail-closed helper on the next field) into that one PR — or stop and list leftovers. Never one-key-per-PR. - When the list is done, STOP. Do not hunt the next milestone, spawn
“hunt remaining / fail-closed holes” agents, or invent
milestones/<one-config-key>.mdas the next task. - If the next patch is the same shape as the last (new milestone file, same helper, different key): hard stop. Report. Do not open another PR.
Stable goals for this repository:
- Embeddable generator —
pip install docgen(git URL or editable install from this repo), a consumer bundle (docgen.yaml+ hints/narration), and shell/CI are enough to build and maintain narrated demos. Do not vendor this library into a product repo’ssrc/; pin viarequirements-docgen.txt/pipx/uv tool. No IDE assistant is required; optionaldocgen wizardis a local web app only. - Hybrid config and prose —
docgen.yamlshould stay maintainable: deterministic merges (yaml-generate, gap checks) plus optional LLM (OpenAI or Grok) where it adds value (narration hints, declarative scene YAML). Prefer Git-reviewed changes over opaque single-shot generation. - Video stack — Long-form demos pair Markdown narration, TTS (OpenAI or xAI), Whisper-style timestamps, Manim visuals,
compose(ffmpeg),concat, andvalidate(sync and narration lint). The CLI also supportspagesfor static preview sites. - Stable contracts — CLI, exit codes, and reusable workflows should stay predictable for downstream repos and automation.
- Library, not app — There is no in-repo dogfood bundle. Consumer projects (e.g.
course-builder) are the integration test of record. The library must not import or special-case any consumer. - Tool-only generation — Narration, merged
docgen.yaml, compiledscenes.py, TTS audio, composed media, and other generated artifacts must come from docgen (CLI/library) and committed wrapper scripts that call it — not from hand-edited outputs passed off as sources. In a consumer bundle, preferhints/*.md+yaml-generateover ad-hoc YAML surgery. Cursor rules:.cursor/rules/docgen-tools-only.mdc,.cursor/rules/no-asset-edits.mdc.
docgen (OpenAI or Grok) is the only path that produces category C outputs — see .cursor/rules/no-asset-edits.mdc. Summary (paths relative to a consumer bundle, typically docs/demos/):
- Outputs (do not hand-edit):
<bundle>/docgen.yaml(as emitted byyaml-generate);<bundle>/narration/*.md(exceptREADME.md);<bundle>/animations/scenes.py,timing.json,animations/specs/*.scene.yaml(scene pipeline);<bundle>/audio/*.mp3;<bundle>/images/*.png(scene image assets fromimage-generate);<bundle>/recordings/**where applicable. - Inputs (maintainer-owned):
<bundle>/hints/**with YAML front matter (docgen.segment,docgen.wiring); maintainer scripts under the bundle;tests/**fixtures inside this library;<bundle>/narration/README.md.
docgen avoids hardcoding consumer segment ids in library code; tests may use concrete fixtures.
Downstream repos that pin this library should:
- Run
docgen yaml-generate(and review the diff). - Regenerate narration, scenes, audio, and video with the documented CLI sequence for their bundle.
- Run
validate/validate --pre-pushbefore pushing.
Pin bumps (pip install …@<sha>) are routine when adopting a new docgen commit.
docgen is a documentation and narrated demo video toolkit: CLI + library focused on Manim (diagram-heavy segments), TTS, timestamps, composition, validation, pages, and wizard-assisted authoring. It is not the product application and not the CI orchestrator for downstream apps.
The Playwright/VHS/demo-function/per-function/discover-tests/catalog surface area was removed; see the README for what is supported today.
Commands registered on the docgen CLI include:
ai-status— print the resolved AI provider and which env var supplied the key (never the secret).init— scaffold bundle layout anddocgen.yaml.wizard— local web UI for narration/bootstrap workflows (focus files, in-place narration revise, per-segment asset freshness + rebuild-from-here, Vue Benchmark view, Tool tab to pip-upgrade docgen and pinrequirements-docgen.txt).gui— desktop window over the same Vue/Flask UI (pip install 'docgen[gui]'for pywebview).--smokeis a headless HTTP check. PyInstaller spec:packaging/docgen-gui.spec. Frozen apps resolve templates/static/benchmark JSON viadocgen.resources.freeze—docgen freezebuilds thedocgen-guionedir (pip install 'docgen[packaging]'). Optional--smokeruns the binary headless. Do not run a full freeze in routine pytest; setDOCGEN_FREEZE_SMOKE=1for the optional test.tts— text-to-speech for segment files (OpenAI or xAI/v1/tts).timestamps— word/segment timing (timing.json). Default enginelocalaligns the known narration text against the mp3 offline (ffmpeg silencedetect, no API);--engine whisperuses OpenAI whisper-1 or xAI/v1/sttwhenai.provideris grok. Both emit the same Whisper-shaped blocks. Failed ffmpeg silencedetect raisesAlignmentError(empty stderr is not treated as full-span speech). OpenAI whisper-1 word/segmentstart/endmust be finite JSON numbers (bool/NaN raiseAIError). Grok/v1/sttrejects empty word tokens and invertedend < startintervals (AIError). Emptysegments.allraisesTimestampError(same as TTS) and does not leave a staletiming.jsonas success.image-generate— render scene-spec image elements (image:+prompt:boxes) via OpenAI Images or xAI Imagine into the bundle (also runs for missing assets insidegenerate-all). Defaultalign_with_docswraps each prompt with narration + source snippets and fails when the authored prompt shares no documented terms. After the PNG is written, OCR plus a vision review (align_review, default true) check the pixels against the same corpus and retry once on FAIL.manim— render Manim scenes declared in config.compose— mux narration audio with visual sources via ffmpeg. With no segment ids, usessegments.all(same asgenerate-all), notsegments.default. Atype: mixedrow raisesComposeErrorif any listed source is missing (no silent subset mux).validate/validate --pre-push— drift, narration lint, Manim hints,timing_sync,story_end(last paced reveal vs audio end; hard fail),scene_assets(pre-render: stuck-board cadence, frame-budget overlaps,MANIM_FONTconsistency, stale helpers / stale compiled class including hand-edited generated-region labels andrun_time— hard fail; also agenerate-allgate before Manim),av_sync(hard fail on--pre-push/generate-all; prefers scene-spec labels as OCR anchors),subject_beat_coverage(declarative specs vs narration topic beats; hard fail when enabled), and related visual-sync checks (ocr_scan,layout,freeze_ratio— hard fail on--pre-push/generate-all).scene_assetsalso fails when an image-element prompt shares no documented terms with narration/source, or when OCR (and optional vision,validation.image_asset_alignment.review) finds invented on-image terms. Missing tesseract failsocr_scan/av_sync/layout(not skip-PASS). Missing audio or an LFS pointer failstiming_syncand recording media gates (stream_presence,av_drift,ocr_scan,av_sync) (not skip-PASS). Missing*.scene.yamlfailsstory_end/subject_beat_coveragefortype: manim(not skip-PASS).lint— narration lint helper.narration-generate— LLM-assisted narration from hints and repo context; optional--revise --revision-notesfor in-place edits (same contract as the wizard Revise button).scene-spec-generate— LLM emits declarative*.scene.yaml; enforces frame budget + subject-beat coverage (dwell OK; cover topic shifts; reject invented labels) and image-prompt alignment (imageprompt:must use documented terms from narration/source).scene-compile— compile specs intoscenes.py(generated regions only).yaml-generate— merge defaults and hint wiring intodocgen.yaml.clean-bundle— remove regenerable outputs per policy.concat— stitch segment videos.pages— emit static HTML for demo assets.generate-all— orchestrated pipeline: TTS → timestamps → scene specs (autoscene-spec-generatewhenanimations/specs/is empty; otherwise offline retime) → images → Manim → compose → validate → concat → pages.--regen-scene-specsforces LLM rewrite;--skip-scene-retimekeeps legacy handscenes.pyonly.rebuild-after-audio— same as generate-all with TTS skipped (still retimes scenes after timestamps).benchmark— score the packaged scene-timing corpus (no bundle / Manim / OpenAI). Executes compiledconstruct()on the real_TimedSceneclock and diffssrc/docgen/benchmark_data/baseline.json. Exit 1 on regression.--update-baselineonly after an intentional improvement.
- Manim /
scenes.py(marker blocks): Fix generators undersrc/docgen/**(manim_scene_support.py,scene_spec.py,scene_spec_generate.py,validate,yaml_generate, tests). Do not patch generated classes inside a consumer'sanimations/scenes.pybetweenBEGIN/END GENERATED SCENEmarkers; re-runscene-spec-generate/scene-compile --retimeandmaniminstead. Preferred consumer order: narration → TTS → timestamps → scene-spec/compile → Manim → compose. The unit CI job runspytest tests/, which includes this module on a temp fixture: a hand-edited generated-region label orrun_timefailsscene_assets. - Beat sync (fail-closed): when
timing.jsonhas words, every story box label must match a spoken phrase (wait_word); unmatched labels and leftover LLM indices are rejected. Opt out withpace: none. Legacy row-levelwait_segmentis upgraded towait_wordand written back onscene-compile(directcompile_scene_classstill rejects leftoverwait_segment). Fuzzy containment matching is not used.scene-compileclamps FadeIn / page-faderun_timeagainst the next word start so_TimedScene._clockcannot race past waits (issue #66 — do not emit cascading first-board dumps). After a reveal, a dwell slot may playIndicate/Circumscribewhen the gap to the nextwait_wordis long enough (also clamped). Long holds emit additional mid-hold pulses (timed_wait+ emphasis) so the board does not freeze after the first Indicate. Optional box fields:shape(rounded/pill/diamond),reveal(fade/grow/slide),emphasis(none/pulse/ring). Page transitions (fade,slide,rise,zoom,none) play the previous page's exit and the next page's first node in onetimed_play(crossfade, slide, rise, or zoom) so the canvas does not empty between them.nonestill removes instantly. They FadeOut those boxes individually, not the parentVGroup. Durations stay clamped like other reveals.scene-compilerefreshes stale_box/_arrow/_TimedScenehelpers inscenes.py. A page may setchart(kind: barorline) instead ofrows. Each series item is its own paced reveal: a bar grows from the left, or a line point fades in and the segment from the previous point is drawn. The value's digits must appear in the narration. - Subject-beat coverage: implemented in
scene_spec.layout_density_violations/cluster_subject_beats; enforced byscene-spec-generateandvalidate(validation.subject_beat_coverage.enabled, default true). Not a blind label count. - Image-prompt alignment: implemented in
image_align.image_prompt_alignment_violations;scene-spec-generateandimage-generatereject prompts that share no documented terms;validatereports the same failures onscene_assets(validation.image_prompt_alignment.enabled, default true).image-generatealso wraps the Images API prompt with narration/source (image_generation.align_with_docs, default true). - Image-asset alignment: OCR + vision review of the generated PNG (
docgen.image_align).image-generatefails closed and retries once (image_generation.align_review, default true).validatefolds the same pixel gate intoscene_assets(validation.image_asset_alignment, OCR on by default, vision opt-in). docgen benchmarkis required after clock / compile /_TimedScene/ dwell changes. Pytest string assertions are not a substitute. Do not remove the CIbenchmarkjob. See Required gate below.- Prefer stable CLI / library contracts and documented exit codes so CI can depend on them.
narration_from_source: hints in config +docgen narration-generate— owner-supplied context paths, not opaque bulk edits to outputs.- Avoid duplicating long orchestration docs here; link to downstream repos when describing their publish pipelines.
Clock, compile, scene-spec motion, _TimedScene helpers, and dwell/hold changes must stay green on the packaged corpus:
- Run
docgen benchmark(or./scripts/benchmark-scenes.sh). Exit 0 vssrc/docgen/benchmark_data/baseline.json. - Keep
tests/test_scene_benchmark.pyin the defaultpytest tests/run. - Keep the
benchmarkjob in.github/workflows/ci.yml(it must invokedocgen benchmark). Do not delete or skip it. --update-baselineonly when the scorecard change is the point of the PR; review the JSON diff. Do not bump the baseline to hide a regression.- When production fails in a new way, add a case to
standard_cases()and update the baseline in the same PR.
String assertions on compiled scenes.py and simulate_reveal_timeline are not a substitute — the harness executes construct() on the real clock.
Tests should cover CLI-visible behavior and contracts that adopters rely on: yaml-generate, scene-spec-generate, scene-compile, validate, compose, generate-all, pages, init, benchmark, gui / freeze (--smoke only in default pytest), freeze-safe docgen.resources paths, config loading (repo_root, env_file), and package exports. Use small in-tree fixtures; this library does not ship a dogfood bundle. Clock / compile changes must keep docgen benchmark at or above benchmark_data/baseline.json — string assertions on scenes.py are not enough. Do not run a full PyInstaller freeze in routine pytest.
- Session contract first: see the top of this file and
.cursor/rules/no-factory-loop.mdc. Finish the assigned list on one PR. Do not ask to merge. Do not start a YAML-key factory or recursive hunt.milestones/README.mdActive is human-assigned only; an empty Active line means stop, not “pick the next config key”. - Virtualenv: the project is installed editable into
/workspace/.venv(created by the startup update script). Shells do not auto-activate it — run. /workspace/.venv/bin/activate(or prefix the venv path) beforedocgen,pytest, orruff. Thedocgenconsole script lives at/workspace/.venv/bin/docgen. - System deps are pre-baked in the VM snapshot (not the update script):
ffmpeg+tesseract-ocr(validation/compose/OCR), plusbuild-essential,python3-dev,libcairo2-dev,libpango1.0-dev,pkg-config(needed to build themanimextra'smanimpango/pycairowheels). If a fresh VM ever lacks these, reinstall via apt beforepip install. - Standard commands are in
README.md/pyproject.toml/.github/workflows/ci.yml: lintruff check src/ tests/; testspytest tests/ -v --tb=short; requireddocgen benchmark(CI jobbenchmark); requireddocgen validateon a scratchinitbundle (CI jobvalidate,scripts/ci-validate-scratch-bundle.sh). The CI unit job also exportsPYTHONPATH=src(not needed locally because of the editable install, but harmless). - OpenAI / Grok / Anthropic vs offline commands:
tts,timestamps --engine whisper,image-generate,narration-generate,scene-spec-generate, andyaml-generate --llmcall a provider. Keys:CURSOR_API_KEY(Cursor Cloud), thenOPENAI_API_KEY(local Cursor, Claude Code, CI). OnlyANTHROPIC_API_KEY→ Claude chat (no TTS/images). Grok:DOCGEN_AI_PROVIDER=grok+XAI_API_KEY.docgen ai-statusprints the resolved key env. Image model isimage_generation.model/--model. Integration tests auto-skip without credentials. Fully offline:init,scene-compile,manim,compose,validate,lint,pages,concat,yaml-generate(no--llm),timestamps(defaultlocalengine), andbenchmark. - Generate against another repo (do not vendor): this environment already has
docgenon PATH. Point at a consumer checkout or clone URL — nothing is copied into that project'ssrc/:docgen --repo /path/to/consumer generate-alldocgen --repo github.com/org/consumer init --defaults--repofindsdocs/demos/docgen.yaml. Add the consumer as a Cloud repository dependency if you clone by GitHub URL (usesGITHUB_TOKEN). Networked stages:CURSOR_API_KEY(Cloud),OPENAI_API_KEY(local / Claude Code),ANTHROPIC_API_KEY(chat), orXAI_API_KEY(Grok). scene-compilegotcha: paced specs (wait_word) need atiming.jsonentry for that stem (docgen timestampsafter TTS). Preferscene-compile --retimeafter fresh timestamps; for a fully offline smoke render, author rows without wait indices only if you accept unpaced reveals.- No in-repo dogfood bundle: exercise the pipeline against a scratch bundle (
docgen init /tmp/<name> --defaultsin a throwaway git dir). Do not hand-edit consumer generated assets (see.cursor/rules/no-asset-edits.mdc). - Wizard / desktop GUI:
docgen wizard --port 8501is the Flask UI (bundle optional for the Benchmark view).docgen gui/docgen benchmark --guiopen the Vue benchmark view in a desktop window whenpywebviewis installed.docgen gui --smokeis the headless HTTP check (default pytest).docgen freezebuilds the GUI onedir; do not run a full PyInstaller freeze in routine CI/pytest. Keeppackaging/docgen-gui.specanddocgen.gui.packagingin sync.