feat(scripts): add dev-sync-audio — scoped sync for audio-lipsync iteration
`dev-sync-audio` is a scoped wrapper around `dev-sync` for the
[`docs/automations/LTX-2.3/audio-lipsync/`](docs/automations/LTX-2.3/audio-lipsync/)
iteration loop. It copies *only* the paths that module touches — the
`forks/whatdreamscost_koolook/` subtree and the root `__init__.py` —
instead of `rmtree`-ing the whole dest `forks/` and re-copying every
Koolook fork on every render. Same `KOLOOK_COMFYUI_DEV_PATH` target,
same ComfyUI-Manager restart, same two-line chat-report shape.
Changes
- `scripts/sync_to_dev_audio.py` (new). Thin wrapper that reuses
`sync_to_dev`'s env loader, target validator, restart helper, and
chat-report builder, supplying its own `AUDIO_PATHS` tuple. Defaults
the `--scope` tag to `"audio-lipsync fork edit"` so the build
identifier names this module even when invoked without an explicit
scope.
- `scripts/sync_to_dev.py` — `sync()` gains an optional `paths` kwarg
(defaults to the existing `RUNTIME_PATHS`). Backwards-compatible with
every existing call site.
- `docs/automations/LTX-2.3/audio-lipsync/README.md` and
`runs/LOOP.md` updated to point at `dev-sync-audio` instead of plain
`dev-sync` for the iteration step.
- Project `CLAUDE.md` — new "Per-module scoped variants" subsection
under the existing `dev-sync` section documents the
`dev-sync-<module>` pattern, the trigger-name → script-name mapping,
and reiterates that the user-initiated-only rule applies verbatim to
every `dev-sync-*` variant.
Verification
- ruff check . → all passed
- python scripts/sync_to_dev_audio.py --dry-run -v →
would copy: __init__.py
would copy: forks/whatdreamscost_koolook
e7f3ab4 - beautiful-mahavira-ae6da6
would sync 2 entries -> <KOLOOK_COMFYUI_DEV_PATH> (dev-sync-audio)
(Confirms scope is correct and the existing dev-sync chat-report
shape is preserved.)
User-initiated only — never automatic. Trigger phrase: `dev-sync-audio`
in chat (also: "copy audio fork", "sync audio fork"). Same rule as
`dev-sync` — see project CLAUDE.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e7f3ab4fe8
commit
15d56882c5
@@ -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-<module>`
|
||||
|
||||
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_<module>.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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 `<target>/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.
|
||||
|
||||
+17
-2
@@ -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
|
||||
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user