diff --git a/CLAUDE.md b/CLAUDE.md index 1c0aa21..1073dab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,6 +103,35 @@ install-missing button on Nodes row Broader iteration pattern (push/publish gates, what survives re-syncs) is in [`docs/maintainers/dev-iteration-loop.md`](docs/maintainers/dev-iteration-loop.md). +### Per-module scoped variants — `dev-sync-` + +When an automation module under `docs/automations/` iterates on code that +lives in a single subtree (e.g. one fork under `forks/`), a *scoped* +dev-sync wrapper avoids churning the rest of the live install on every +iteration. Same `KOLOOK_COMFYUI_DEV_PATH` target, same restart, same +chat-report shape — just a smaller set of paths copied. + +Current per-module variants: + +- **`dev-sync-audio`** (chat phrase) → `python scripts/sync_to_dev_audio.py`. + Copies `forks/whatdreamscost_koolook/` + root `__init__.py` only. + Driven by the + [`docs/automations/LTX-2.3/audio-lipsync/`](docs/automations/LTX-2.3/audio-lipsync/) + iteration. Leaves `forks/radiance_koolook/` and the rest of the live + install alone across iterations. + +When a new code-touching automation module is added, follow the same +pattern: a `scripts/sync_to_dev_.py` wrapper that imports the +shared infrastructure from `sync_to_dev` and supplies its own +`PATHS` tuple. The chat trigger is the file name minus `sync_to_dev_` +and minus `.py`, with the underscore between module segments turned +into a hyphen: `sync_to_dev_audio.py` → `dev-sync-audio`. Document the +new trigger in this section *and* in the module's `runs/LOOP.md`. + +**All scoped variants follow the same user-initiated-only rule** as +plain `dev-sync` (the bullet list above applies verbatim). Never run any +`dev-sync-*` from an automated flow. + ## Kforge Labs sidebar — starter preset distribution The "Kforge Labs" ComfyUI sidebar tab (implemented in diff --git a/docs/automations/LTX-2.3/audio-lipsync/README.md b/docs/automations/LTX-2.3/audio-lipsync/README.md index 6da934e..3e0cc70 100644 --- a/docs/automations/LTX-2.3/audio-lipsync/README.md +++ b/docs/automations/LTX-2.3/audio-lipsync/README.md @@ -83,11 +83,12 @@ audio-lipsync/ ## Iteration loop 1. **Edit a knob.** Either: - - **Widget-only swap** — change `relay_overrides` JSON on the LTXDirector node in ComfyUI. No restart needed; just queue. + - **Widget-only swap** — change `relay_overrides` JSON on the LTXDirector node in ComfyUI. No sync, no restart; just queue. - **Code-level change** — edit [`../../../../forks/whatdreamscost_koolook/versions/v1_3_2/prompt_relay.py`](../../../../forks/whatdreamscost_koolook/versions/v1_3_2/prompt_relay.py) or [`ltx_director.py`](../../../../forks/whatdreamscost_koolook/versions/v1_3_2/ltx_director.py) - in this repo. Then sync into the maintainer's live ComfyUI via the standard `dev-sync` (user-initiated; see project `CLAUDE.md`). Restart Comfy for Python module reload. + in this repo. Then run **`dev-sync-audio`** (chat phrase or directly + [`scripts/sync_to_dev_audio.py`](../../../../scripts/sync_to_dev_audio.py)) — a scoped variant of `dev-sync` that copies just the fork dir + the root `__init__.py` into the live ComfyUI install at `$KOLOOK_COMFYUI_DEV_PATH`, leaves `forks/radiance_koolook/` and the rest of the tree alone, then triggers a ComfyUI-Manager reboot so the Python module re-imports. User-initiated only — same rule as `dev-sync`; see project `CLAUDE.md`. 2. **Save workflow** — `Workflow → Save (API Format)` into the working folder. 3. **Render** — queue. 4. **Report** in chat — verbal feedback on sync state, motion, prompt adherence. @@ -119,5 +120,6 @@ install is untouched. Edits to `forks/whatdreamscost_koolook/versions/v1_3_2/*.py` are normal git-tracked changes — `git restore` / `git stash` work as usual. Once -`dev-sync` is run, the running ComfyUI install is updated; restart Comfy -so the Python module reimports. +`dev-sync-audio` is run, the running ComfyUI install is updated; the +auto-restart triggered at the end of the sync re-imports the Python +module. diff --git a/docs/automations/LTX-2.3/audio-lipsync/runs/LOOP.md b/docs/automations/LTX-2.3/audio-lipsync/runs/LOOP.md index c98f4ca..e73566d 100644 --- a/docs/automations/LTX-2.3/audio-lipsync/runs/LOOP.md +++ b/docs/automations/LTX-2.3/audio-lipsync/runs/LOOP.md @@ -10,7 +10,7 @@ | Step | Who | What | |---|---|---| -| 1 | Maintainer | Either edit `relay_overrides` JSON on the **`LTX Director (Koolook v1.3.2)`** node in ComfyUI, OR edit a `forks/whatdreamscost_koolook/versions/v1_3_2/*.py` source in this repo + run `dev-sync` + restart Comfy. | +| 1 | Maintainer | Either edit `relay_overrides` JSON on the **`LTX Director (Koolook v1.3.2)`** node in ComfyUI, OR edit a `forks/whatdreamscost_koolook/versions/v1_3_2/*.py` source in this repo + run **`dev-sync-audio`** (auto-restarts Comfy). | | 2 | Maintainer | **Save** workflow → overwrites the current workflow file at the working folder. | | 3 | Maintainer | Queue render in ComfyUI. | | 4 | Maintainer | Report result in chat (verbal feedback — sync state, motion state, prompt adherence). | @@ -51,18 +51,39 @@ Auto-generated from key knob state + feedback summary, e.g.: The maintainer can override any label by stating it explicitly. -## Syncing fork-code edits to the live install +## Syncing fork-code edits to the live install — `dev-sync-audio` When the iteration step requires editing a `forks/whatdreamscost_koolook/` -source (not just the widget), the standard -[`dev-sync`](../../../../scripts/sync_to_dev.py) flow applies — it's a -user-initiated, never-automatic action per the project `CLAUDE.md`. Quick -recipe: +source (not just the widget), run the **scoped** sync for this module — +not the full `dev-sync`: ``` -python scripts/sync_to_dev.py --scope "audio-lipsync fork edit" +python scripts/sync_to_dev_audio.py +# or just say "dev-sync-audio" in chat — the agent runs the script ``` -Then restart Comfy so the Python module re-imports. Widget-only changes -(editing `relay_overrides` JSON on the canvas) do **not** require sync or -restart — just queue the next render. +What it does: +- Copies only `forks/whatdreamscost_koolook/` + the root `__init__.py` + to `$KOLOOK_COMFYUI_DEV_PATH` (read from `.env`). +- Leaves the rest of the live install (radiance fork, the root `k_*.py` + nodes, the sidebar `web/` bundle, `video_formats/`) untouched. +- Auto-triggers a ComfyUI-Manager reboot so the Python module re-imports + (custom-node `.py` files load once at server start; without a restart + the new code stays invisible). Use `--no-restart` to opt out. +- Writes the scope tag into `/web/_dev_build.json` so the + Kforge Labs sidebar footer shows which build is live. + +Flags mirror `dev-sync`: `--dry-run`, `--init`, `--verbose`, +`--scope "<≤10-word change>"`, `--no-restart`, `--restart-url`. + +User-initiated only — same rule as `dev-sync` (see project `CLAUDE.md`). +Never automatic. Never on commit, never on session end. + +Widget-only changes (editing `relay_overrides` JSON on the canvas) do +**not** require sync or restart — just queue the next render. + +Why not full `dev-sync`? Because it `rmtree`s the entire dest `forks/` +tree and re-copies both Koolook forks. For this module's iteration +loop, that churns `forks/radiance_koolook/` for no reason. Scoping to +`forks/whatdreamscost_koolook/` keeps the radiance side of the live +install stable across audio-lipsync iterations. diff --git a/scripts/sync_to_dev.py b/scripts/sync_to_dev.py index 9d7493d..26c48ea 100644 --- a/scripts/sync_to_dev.py +++ b/scripts/sync_to_dev.py @@ -251,9 +251,24 @@ def _ignore(_dir: str, names: list[str]) -> list[str]: return [n for n in names if n in DIR_EXCLUDES] -def sync(target: Path, dry_run: bool, verbose: bool) -> int: +def sync( + target: Path, + dry_run: bool, + verbose: bool, + paths: tuple[str, ...] = RUNTIME_PATHS, +) -> int: + """Copy each entry in ``paths`` (relative to the repo root) to the + matching subpath under ``target``. Directories are recursively + copied with ``DIR_EXCLUDES`` filtered out and the previous dest + subtree removed first; files are overwritten in place. + + The optional ``paths`` argument lets scoped wrappers (e.g. + ``sync_to_dev_audio.py``) reuse this function with a smaller set — + just the subtree their automation module touches. The default is + the full ``RUNTIME_PATHS`` (every file ComfyUI loads at runtime). + """ copied = 0 - for rel in RUNTIME_PATHS: + for rel in paths: src = REPO_ROOT / rel if not src.exists(): continue diff --git a/scripts/sync_to_dev_audio.py b/scripts/sync_to_dev_audio.py new file mode 100644 index 0000000..63d5cc3 --- /dev/null +++ b/scripts/sync_to_dev_audio.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +""" +Scoped variant of ``dev-sync`` for the LTX 2.3 audio-lipsync automation. + +Copies only what that automation's iteration loop touches: + + - forks/whatdreamscost_koolook/ the Koolook fork of LTXDirector + + its modified prompt_relay + the + verbatim-vendored patches.py + - __init__.py root loader (registers the fork's + node mappings; needs to be re-synced + when the fork's NODE_CLASS_MAPPINGS + set changes) + +Everything else in the live install — ``forks/radiance_koolook/``, the +root ``k_*.py`` nodes, ``web/``, ``video_formats/`` — is left untouched. + +USER-INITIATED ONLY. Same rule as plain ``dev-sync`` (see project +``CLAUDE.md`` → ``dev-sync`` section). Never run automatically: + + * after a commit + * after a PR merge or ``/ship-pr`` + * at session end / wrap-up + * on hook completion or "task complete" cleanup + * from any agent skill that doesn't explicitly require it + +Run only on the explicit user trigger phrase ``dev-sync-audio`` (or +``copy audio fork``, ``sync audio``, etc.). The maintainer typically has +multiple parallel sessions across worktrees; an unsolicited sync from one +silently destroys what another is reviewing. + +The target path comes from ``KOLOOK_COMFYUI_DEV_PATH`` in ``.env`` — +identical to plain ``dev-sync``. No new env var. + +Usage: + python scripts/sync_to_dev_audio.py + python scripts/sync_to_dev_audio.py --dry-run + python scripts/sync_to_dev_audio.py --scope "vstr=10 trial" + python scripts/sync_to_dev_audio.py --no-restart + +Exit codes mirror ``sync_to_dev.py`` exactly: + 0 success + 2 KOLOOK_COMFYUI_DEV_PATH unset or target missing without --init + 3 --init refused + +After copying, the script triggers a ComfyUI-Manager reboot so the new +Python files are actually loaded (custom-node modules load once at server +start; a file-only sync leaves ``.py`` changes invisible). Use +``--no-restart`` to opt out. +""" +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path + +# Reuse the dev-sync infrastructure verbatim — same ``.env`` loader, +# same target validation, same restart, same chat-report shape — so the +# only thing that differs between full and scoped syncs is *which paths +# get copied*. Per-module wrappers stay small and the safety guarantees +# (target validation, parent-directory sanity check, restart fallbacks) +# only need to be audited in one place. +_SCRIPT_DIR = Path(__file__).resolve().parent +if str(_SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPT_DIR)) +import sync_to_dev as _dev # noqa: E402 + + +AUDIO_PATHS: tuple[str, ...] = ( + "__init__.py", + "forks/whatdreamscost_koolook", +) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--dry-run", + action="store_true", + help="Print what would be copied without touching the target.", + ) + parser.add_argument( + "--init", + action="store_true", + help=( + "Create the target directory if it doesn't exist. The parent " + "(typically a ComfyUI custom_nodes/ folder) must already be " + "in place. Use on first-run only." + ), + ) + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="Print each file/dir as it's copied.", + ) + parser.add_argument( + "--scope", + type=str, + default="audio-lipsync fork edit", + help=( + "Short (≤10 word) description for the chat report's second line " + "and the in-browser footer in `web/_dev_build.json`. Defaults to " + "'audio-lipsync fork edit' so the build identifier always names " + "this module even when the maintainer doesn't pass a scope." + ), + ) + parser.add_argument( + "--no-restart", + action="store_true", + help=( + "Skip the auto-restart of the live ComfyUI server. Without a " + "restart, the new fork code stays loaded as the previous version " + "(custom-node .py files load once at server start)." + ), + ) + parser.add_argument( + "--restart-url", + type=str, + default=_dev.DEFAULT_RESTART_URL, + help=( + f"Override the restart endpoint. Default: {_dev.DEFAULT_RESTART_URL}. " + f"Ignored when --no-restart is set or --dry-run is used." + ), + ) + args = parser.parse_args() + + _dev.load_dotenv(_dev.REPO_ROOT / ".env") + + target_str = os.environ.get("KOLOOK_COMFYUI_DEV_PATH") + if not target_str: + print( + "KOLOOK_COMFYUI_DEV_PATH not set. Add it to .env (see .env.example).", + file=sys.stderr, + ) + return 2 + + target = Path(target_str).expanduser() + err = _dev.ensure_target(target, init=args.init) + if err is not None: + return err + + n = _dev.sync( + target, + dry_run=args.dry_run, + verbose=args.verbose, + paths=AUDIO_PATHS, + ) + verb = "would sync" if args.dry_run else "synced" + print(_dev._build_line()) + print(f"{verb} {n} entries -> {target} (dev-sync-audio)") + if not args.dry_run: + _dev.write_build_info(target, args.scope) + if not args.no_restart: + _ok, msg = _dev.trigger_restart(args.restart_url) + print(msg) + return 0 + + +if __name__ == "__main__": + sys.exit(main())