feat(atelier): post-render review hook + reviewer enforcement (F6 + F7)
Closes the two biggest gaps from the atelier audit: hero bespoke renders
were skipping every automated quality gate, and the templated→atelier
inversion was advisory rather than enforced.
F6 — _render_via_atelier now runs the full _run_final_review (technical
probe + visual spotcheck + audio spotcheck + duration drift + transcript
comparison) plus two atelier-specific doctrine checks:
- stock_reuse_check: rglobs the bespoke project tree for imports from
the stock creative registry (src/components, src/Explainer, etc.).
Any hit fails the render with status="fail" and names the offending
file — the doctrine bypass is now mechanically caught.
- art_direction_check: requires edit_decisions.bespoke.art_direction.
Missing → warning surfaced in final_review.issues_found; forcing
function for step 1 of skills/meta/bespoke-composition.md.
Verified end-to-end on the compound-snowball proof render: positive case
status=pass, atelier checks clean; negative case (deliberate stock import
+ missing art_direction) returns status=fail with both findings named.
F7 — skills/meta/reviewer.md gets a "Composition Authoring Mode Review"
section that enforces the inversion at every stage:
- proposal: composition_mode must be a presented decision with both
options; hero briefs default to atelier; templated for hero requires
an explicit sanctioned reason.
- scene_plan/edit: any stock cut.type in an atelier piece is CRITICAL;
bespoke.{entry, composition_id, art_direction} required.
- compose: consumes final_review.checks.atelier from F6; mandates a
recorded distinctness review (the human taste call the tool can't
automate — its absence is itself CRITICAL).
- publish: blocks ship on any unresolved atelier finding.
Engine knowledge stays reusable; creative components don't.
This commit is contained in:
@@ -305,3 +305,33 @@ Run at **compose** and **publish** stages. Ensures the agent reviewed the actual
|
||||
1. Verify that `final_review` was passed through as a required artifact
|
||||
2. If `final_review.status` is not `pass`: **CRITICAL** — "Cannot publish with a non-passing self-review"
|
||||
3. If `final_review.issues_found` is non-empty and `recommended_action` is not `present_to_user`: **SUGGESTION** — "Self-review found issues; verify they were resolved before publishing"
|
||||
|
||||
## Composition Authoring Mode Review
|
||||
|
||||
The templated→atelier inversion (`AGENT_GUIDE.md` → "Composition Authoring Mode" + `skills/meta/bespoke-composition.md`) is governance, not a suggestion. The reviewer is the enforcement point: without these checks, the next agent quietly defaults back to the stock cut-schema and every video starts looking the same again.
|
||||
|
||||
### At proposal stage:
|
||||
1. `decision_log` must contain a `composition_mode` decision with `options_considered: ["templated","atelier"]` and a `selected` value with a real reason tied to the brief.
|
||||
- Missing `composition_mode` decision entirely: **CRITICAL** — "Proposal missing composition_mode choice. Atelier vs templated is a mandatory presented decision (see AGENT_GUIDE.md → Composition Authoring Mode)."
|
||||
- Decision logged with only one option considered: **CRITICAL** — "composition_mode decision logged without presenting both templated and atelier alternatives."
|
||||
2. For **hero work** (brief tagged marketing / launch / brand piece / explainer-with-quality-bar / any single-deliverable where quality is the point) where `selected == "templated"`: **CRITICAL** — "Hero brief locked composition_mode='templated'. Default is atelier per doctrine; templated requires an explicit reason in `decision_log.<entry>.reason` (e.g. localization variant, batch, time-boxed draft)." Only suppress if the reason field names a sanctioned exception.
|
||||
3. If `composition_mode == "atelier"` and `proposal_packet` lacks an `art_direction` declaration (palette, type, motion, signature device): **CRITICAL** — "Atelier proposal missing art-direction commitment. Per `skills/meta/bespoke-composition.md` step 1, art direction must be written down *before* authoring scenes."
|
||||
|
||||
### At scene_plan / edit stage (when composition_mode == "atelier"):
|
||||
1. `edit_decisions.composition_mode` must equal `"atelier"` and `edit_decisions.bespoke.{entry, composition_id, art_direction}` must all be set.
|
||||
- Missing any of `entry`/`composition_id`: **CRITICAL** — "Atelier compose contract incomplete; render will be rejected by `_render_via_atelier`."
|
||||
- Missing `art_direction`: **CRITICAL** — "Atelier without an art-direction declaration; reviewer cannot evaluate distinctness."
|
||||
2. Any presence of stock `cut.type` scene-types (`text_card`, `stat_card`, `bar_chart`, `kpi_grid`, `callout`, `comparison`, `hero_title`, `terminal_scene`, `anime_scene`, `progress_bar`, `pie_chart`, `line_chart`) in `edit_decisions.cuts`: **CRITICAL** — "Atelier piece reaches for stock cut.type {name}. Hand-author the scene; the stock registry is a mechanics codex, not a parts bin (`skills/meta/bespoke-composition.md`)."
|
||||
|
||||
### At compose stage (when composition_mode == "atelier"):
|
||||
1. The compose stage's `final_review.checks.atelier` block must exist. If absent: **CRITICAL** — "Atelier render skipped doctrine checks — `_render_via_atelier` returned without `atelier` checks; investigate tool wiring."
|
||||
2. If `final_review.checks.atelier.stock_reuse_detected == true`: **CRITICAL** — "Stock-registry import inside bespoke project ({offending_imports[0].file} → {offending_imports[0].import}). Hand-author the scene; do not import from the stock src/."
|
||||
3. If `final_review.checks.atelier.art_direction_declared == false`: **CRITICAL** — "Atelier render with no art-direction declaration. Set `edit_decisions.bespoke.art_direction` before re-render."
|
||||
4. **Distinctness review (human-judged, mandatory).** Before approving the render, the reviewer must explicitly answer in the review record:
|
||||
- *"Could this video be any other product's video?"* (yes ⇒ CRITICAL, re-author art direction)
|
||||
- *"Does its visual language reuse a look from a prior piece I've made?"* (yes ⇒ CRITICAL, re-author)
|
||||
- *"Is the signature device named in `art_direction` actually present in the rendered frames?"* (no ⇒ CRITICAL, re-author or update the declaration to match what was actually built)
|
||||
Distinctness is taste-call territory the tool can't automate; reviewer absence on this question is itself a **CRITICAL** finding ("distinctness review not recorded").
|
||||
|
||||
### At publish stage (when composition_mode == "atelier"):
|
||||
1. All four atelier compose-stage findings above must show `resolved` in the review record. Any unresolved: **CRITICAL** — "Cannot publish atelier piece with unresolved doctrine or distinctness findings."
|
||||
|
||||
+122
-10
@@ -789,18 +789,130 @@ class VideoCompose(BaseTool):
|
||||
error=f"Atelier render completed but output file missing: {output_path}",
|
||||
)
|
||||
|
||||
return ToolResult(
|
||||
success=True,
|
||||
data={
|
||||
"operation": "render",
|
||||
"composition_mode": "atelier",
|
||||
"entry": str(entry_path),
|
||||
"composition_id": comp_id,
|
||||
"output": str(output_path),
|
||||
},
|
||||
artifacts=[str(output_path)],
|
||||
# --- Atelier post-render review -------------------------------------
|
||||
# The cut-schema paths run _run_final_review (technical/visual/audio
|
||||
# probes + transcript-vs-script). Atelier MUST do the same so hero
|
||||
# renders aren't shipped without the safety net — and additionally
|
||||
# enforce the bespoke doctrine: no stock-registry imports, an
|
||||
# art-direction declaration must exist. The distinctness review
|
||||
# ("could this be any other product's video?") stays human; what we
|
||||
# automate here is the *doctrine bypass*, not the taste call.
|
||||
final_review = self._run_final_review(
|
||||
output_path=output_path,
|
||||
edit_decisions=edit_decisions,
|
||||
proposal_packet=inputs.get("proposal_packet"),
|
||||
narration_transcript_path=inputs.get("narration_transcript_path"),
|
||||
script_text=inputs.get("script_text"),
|
||||
)
|
||||
|
||||
atelier_checks = self._run_atelier_checks(entry_path, bespoke)
|
||||
final_review.setdefault("checks", {})["atelier"] = atelier_checks
|
||||
final_review["issues_found"] = list(final_review.get("issues_found", [])) + atelier_checks.get("issues", [])
|
||||
|
||||
# Escalate atelier-critical issues (stock reuse) to the overall status.
|
||||
# Missing art-direction is a warning, not a fail — it shows in issues_found.
|
||||
if atelier_checks.get("stock_reuse_detected"):
|
||||
final_review["status"] = "fail"
|
||||
final_review["recommended_action"] = "re_author"
|
||||
|
||||
data: dict[str, Any] = {
|
||||
"operation": "render",
|
||||
"composition_mode": "atelier",
|
||||
"entry": str(entry_path),
|
||||
"composition_id": comp_id,
|
||||
"output": str(output_path),
|
||||
"final_review": final_review,
|
||||
"final_review_status": final_review.get("status"),
|
||||
}
|
||||
|
||||
if final_review.get("status") == "fail":
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error=(
|
||||
"Atelier render produced an invalid output:\n"
|
||||
+ "\n".join(f" • {i}" for i in final_review.get("issues_found", []))
|
||||
),
|
||||
data=data,
|
||||
artifacts=[str(output_path)],
|
||||
)
|
||||
|
||||
return ToolResult(success=True, data=data, artifacts=[str(output_path)])
|
||||
|
||||
# Stock-registry import patterns that violate the atelier doctrine.
|
||||
# Any of these inside a bespoke project tree means a creative component
|
||||
# was reused instead of hand-stitched. Engine knowledge (the `remotion`
|
||||
# package, `@remotion/*`, project-local files) is fine.
|
||||
_ATELIER_STOCK_IMPORT_RE = (
|
||||
r"""from\s+["']("""
|
||||
# parent-traversed paths into the stock src/
|
||||
r"""(?:\.\./)+src/(?:components|Explainer|CinematicRenderer|"""
|
||||
r"""TitledVideo|TalkingHead|CollageBurst|LyricOverlay|cinematic|crucix|phantom)"""
|
||||
# or absolute-ish paths into the same
|
||||
r"""|remotion-composer/src/(?:components|Explainer|CinematicRenderer|"""
|
||||
r"""TitledVideo|TalkingHead|CollageBurst|LyricOverlay|cinematic|crucix|phantom)"""
|
||||
r""")"""
|
||||
)
|
||||
|
||||
def _run_atelier_checks(self, entry_path: Path, bespoke: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Doctrine-enforcement checks specific to atelier mode.
|
||||
|
||||
Returns a dict with two checks:
|
||||
- stock_reuse_detected (bool) + offending_imports (list) — CRITICAL,
|
||||
fails the render. Catches `import X from "../../src/components/..."`
|
||||
and similar reuse of stock creative components.
|
||||
- art_direction_declared (bool) + art_direction (str|None) — WARNING.
|
||||
Forces step 1 of the bespoke-composition skill (commit to a fresh
|
||||
art direction per video) to be written down rather than skipped.
|
||||
"""
|
||||
import re as _re
|
||||
|
||||
issues: list[str] = []
|
||||
offending: list[dict[str, str]] = []
|
||||
project_dir = entry_path.parent
|
||||
pat = _re.compile(self._ATELIER_STOCK_IMPORT_RE)
|
||||
|
||||
try:
|
||||
for f in project_dir.rglob("*"):
|
||||
if not f.is_file() or f.suffix.lower() not in {".tsx", ".ts", ".jsx", ".js"}:
|
||||
continue
|
||||
try:
|
||||
txt = f.read_text(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
continue
|
||||
for m in pat.finditer(txt):
|
||||
offending.append({"file": str(f.relative_to(project_dir)), "import": m.group(1)})
|
||||
except Exception as e: # pragma: no cover — never let the check itself break a render
|
||||
issues.append(f"atelier stock-reuse scan errored: {e}")
|
||||
|
||||
stock_reuse_detected = bool(offending)
|
||||
if stock_reuse_detected:
|
||||
issues.append(
|
||||
"atelier doctrine violation: bespoke project imports from the stock "
|
||||
"creative registry. Hand-author the scene instead — the registry is "
|
||||
"a mechanics codex, not a parts bin. Offending imports: "
|
||||
+ ", ".join(f"{o['file']} → {o['import']}" for o in offending[:5])
|
||||
+ ("…" if len(offending) > 5 else "")
|
||||
)
|
||||
|
||||
art_direction = bespoke.get("art_direction") or bespoke.get("art_direction_note")
|
||||
art_direction_declared = bool(art_direction and str(art_direction).strip())
|
||||
if not art_direction_declared:
|
||||
issues.append(
|
||||
"atelier warning: no bespoke.art_direction declared. Per "
|
||||
"skills/meta/bespoke-composition.md step 1, every atelier piece must "
|
||||
"commit to a fresh art direction (palette, type, motion, signature "
|
||||
"device) before authoring. Pass edit_decisions.bespoke.art_direction "
|
||||
"as a short note or a path to art-direction.md."
|
||||
)
|
||||
|
||||
return {
|
||||
"stock_reuse_detected": stock_reuse_detected,
|
||||
"offending_imports": offending,
|
||||
"art_direction_declared": art_direction_declared,
|
||||
"art_direction": str(art_direction) if art_direction else None,
|
||||
"issues": issues,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _build_theme_from_playbook(
|
||||
playbook_name: str | None,
|
||||
|
||||
Reference in New Issue
Block a user