Files
malkuthro-ComfyUI-Koolook/scripts/sync_to_dev_audio.py
T
malkuthroandClaude Opus 4.7 3ffb7b75aa fix(review): harden relay_overrides parser + polish (PR #178 review)
Addresses verified findings from the /review-pr pass on PR #178.

H1 — relay_overrides values are now type-checked
- Allowlist of recognised knobs (`video_strength`, `video_window_scale`,
  `audio_strength`, `audio_window_scale`, `audio_epsilon`) with explicit
  float-coercion. Numeric strings ("10") coerce silently; non-numeric
  values (e.g. "ten") now raise a ValueError naming the offending knob
  and showing a minimal correct example — instead of a TypeError deep
  inside `build_segments` arithmetic, or a SUCCESS render at upstream
  defaults that polluted the A/B iteration log.

H2 — malformed JSON no longer silently falls back to defaults
- `json.JSONDecodeError`, non-dict JSON (arrays, strings, bare numbers,
  null), and non-numeric values for recognised knobs all now raise
  `ValueError` with operator-actionable messages. The only silent
  fallback is the explicit empty/whitespace case — "use upstream
  defaults" is the operator's intent there.
- Typo'd keys (e.g. `viedo_strength`) log a warning naming the unknown
  keys + the recognised set, then drop the typo. Previously they were
  silently retained in the cleaned dict but never queried by
  `build_segments`, indistinguishable from a default render.

Architectural side-effect — shrinks the diff vs vendored upstream
- `_parse_relay_overrides` extracted into a stdlib-only sibling module
  `_relay_overrides.py`. `ltx_director.py` shrinks closer to upstream
  baseline (one import + one alias instead of a 30-line function body)
  and is easier to diff against `e81223a`. The parser is also now
  unit-testable without ComfyUI's `comfy_api` runtime.

L5 — tests added (31 cases, all passing)
- New `tests/forks/__init__.py` + `tests/forks/test_whatdreamscost_v1_3_2_relay_overrides.py`.
- Covers every confirmed failure mode (H1 + H2), the happy path, the
  comment-key convention, the unknown-key warning, and an allowlist-
  consistency assertion that fails loudly if `build_segments` ever
  grows a knob without the parser being updated.
- Test module uses `importlib.util.spec_from_file_location` so it can
  load the parser without walking through the package hierarchy (which
  would trigger the `comfy_api` import inside `ltx_director.py`).

L3 — `_build_line` promoted to public `build_line`
- `scripts/sync_to_dev.py` renames the private helper. The underscore
  alias is kept for one release cycle as a deprecation slot.
- `scripts/sync_to_dev_audio.py` switches to `_dev.build_line()`. No
  behaviour change; chat-report shape is bit-identical to before.

L2 — `source_ref` clarified in pin metadata
- `forks/whatdreamscost_koolook/versions/v1_3_2/UPSTREAM_PIN.yaml` and
  `forks/forks_manifest.yaml` both now spell out that `e81223a` is the
  post-v1.3.2 README-only commit on upstream's `main`, not the v1.3.2
  release tag itself. Code is byte-identical to the tagged release; the
  pin differs only in trailing docs.

L1, L4 are explicitly left alone (L1 is dead upstream cruft, kept for
minimum-diff; L4 is helper duplication tracked for a separate follow-up
issue when a third fork lands).

Verification
- ruff check . → clean
- pytest → 204 passed (173 prior + 31 new parser tests)
- tools/preflight_release.py --skip manager-meta → all PASS (static AST
  extraction still picks up LTXDirector from the fork)
- scripts/sync_to_dev_audio.py --dry-run → two-line chat report still
  emits via the renamed `build_line` helper

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:30:50 +02:00

163 lines
5.5 KiB
Python

#!/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())