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>
163 lines
5.5 KiB
Python
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())
|