review · git:20261011.099fd37 · 2026-10-11 · sha256 800339caf7fa5609

review git:20261011.099fd37A

Immutable. This exact content never changes and is served at /api/v1/blob/800339caf7fa5609.

---
name: review
description: Review a BashCut edit before export as a capped loop — set the review profile from sourced ranges, measure, look in batches, judge findings by what a viewer perceives, fix one labelled edit per finding, prove each fix with review verify, and report what was and was not checked; an independent critic (or a self-audit from the packet) is always part of it, and the skill is also the critic's brief for a review packet. Use at the review stage, before a draft or export, or when asked to check a video. Triggers: "review", "kiểm tra video", "soát lỗi", "xem lại bản dựng", "chấm bản dựng", "critic", "QC", "trước khi xuất".
---

# Reviewing an edit in BashCut

Reply in the user's language. Core measures, renders evidence, tracks issue identity and rounds, and states what it
did not check. Which findings matter and how many rounds to run are decided here, by you, for this video.

Passing checks is not proof of a good video. One tool's edit had all 29 actions on the beat and every cut on a bar
line, and still had cramped frames, small text and titles over the icons: "the model will satisfy whatever can be
quantified" (T16 §2, guizang). Look at the pixels.

## If you were given a packet

You are a fresh critic: you got a folder from `review packet --point strategy|draft|process` and this skill, nothing
else. You did not make the edit and you have none of the maker's reasons, on purpose (T16 §4). `README` says which
point you audit. Judge what is there against the brief and the plan, never against what the maker meant.

| Point | You get | You check |
|---|---|---|
| `strategy` (before the rough cut) | brief (with `inferred` fields), plan (options, `promise`, sections, `checks`), a story sheet | one message; the payoff answers the hook; fits the brief; each `inferred` field is a fair guess; the recipe checks that apply to a plan |
| `draft` (before G5) | the files below + `plan.checks` | the generic checks and `plan.checks` as a viewer; the measured issues |
| `process` (at the end) | `run checklist`, `run log`, `timeline changes` | below, "Process audit" |

**Generic checks** (every video, whatever the recipe): one message; `promise.payoff` answers `promise.hook`; text
and speech in the opening (`hookSeconds`) say the same thing; understandable with the sound off; captions never repeat text that
is on screen; nothing on screen the brief did not ask for: no credits, no "AI voice", "minh hoạ", "stock" or effect labels.

The draft packet:

| File | What it holds | Use it to |
|---|---|---|
| `README` | what the packet is, the project, revision and round | know which round you judge |
| `plan.json` | the brief, the edit plan (sections with length ranges, shots, beats, decisions), the review profile, the outputs | know what the edit promised and for which platforms |
| `digest.json` | what changed since the last review round | in round 2+, check those changes first |
| `issues.json` | the measured issues with severity, frame, fix, and the round diff (fixed, new, persisting) | start from facts; a measured issue is a number, not yet a finding |
| `shots.json` | every shot on Main with the cut into it (kind, framing before and after), rhythm, shares | find jump cuts, same-framing cuts, unintended gaps, monotony |
| `word-landing.json` | words against cuts and titles: offsets, cuts inside words | find clipped words, titles early or late on their word |
| `coverage.json` | the described shot each clip plays; the plan's beats against the words heard | find promised shots or lines that are missing (join the clips with `plan.json` shots) |
| `measured.json` | what was measured and what was not (stale, notChecked, failed, unreliable) | never pass what nobody measured |
| `sheet-*.png` | a contact sheet of every cut and title, labelled `<cell> <m:ss.s>` | see framing, text size and placement, repeated shots |

How to work: read `README`, `plan.json` and `measured.json` first; then `issues.json`, `shots.json`,
`word-landing.json`, `coverage.json`; then every sheet; judge the opening from the first shots, words and cells. Do not edit the project. If you also have the
`bashcut` CLI, read-only commands are fine for a doubt (`ui frame F --phone`, `review window F`), nothing else.

Return, as text, briefed "to roast, not to praise" (T16 §2):

1. A verdict: `pass` (no blocker), `changes` (blockers the maker can fix) or `fail` (the strategy or the cut is
   wrong). A check you could not judge is not a pass: name it under 5.
2. Findings, blockers first: time (m:ss.s) or frame, severity (below), what the viewer perceives, the evidence (file
   and field, or sheet cell), and which stage owns the fix (story, rough cut, rhythm, sound, text, colour, effects).
3. The 3–5 fixes to do first (T16 §7).
4. `needs_user`: taste choices and missing material, kept apart from defects.
5. What you could not judge: sound by ear, motion between sheet cells, anything `measured.json` lists as not measured.

Record the verdict (it is a log entry, not an edit): `run append audit --point draft --verdict changes --findings 4
--by critic`. Without the CLI, return it and the maker records exactly your verdict and count with `--by critic`.

### Process audit

Read the checklist first, then the log and the changes. Flag: a required stage `skipped` or missing; `done` without
evidence or with evidence that is not in the log; a skill marked unread for a stage that ran; a missing or `self`
audit; a gate request without the user's answer; claims in the summary (`end`, gate summaries) that no entry or
change backs ("fixed" without `review verify`, "exported" without an export). Return the verdict as above and the
lessons, each as symptom → cause → what to do, for `bc:self-learn`.

## Severity: what the viewer perceives

Define severity by the viewer at normal speed and size on a phone, not by the metric (T16 §4, reelmimic, talkcraft):

- **blocker**: a viewer will notice it at normal speed and it hurts understanding or trust: a black or frozen
  frame, a clipped word, unreadable or covered text, speech drowned by music, a missing promised moment, a hook that
  does not show what the video is about. Core `error` issues (gaps, black picture on Main, a cut inside a word,
  missing glyphs, wrong length or shape for an output) are blockers unless you show they are not real.
- **polish**: visible only when paused, zoomed or compared side by side. Fix when cheap; never a reason for another round.
- **taste**: a choice a viewer could like either way (pace, a look, a transition). Leave it to the user (`needs_user`).
- **needs user**: something only the user can supply or decide (a missing shot, music choice, a name to spell).

A measured warning is not automatically a blocker: a locked-off interview trips "static shot"; a deliberate long
title trips "long still". Decide, then fix, adjust the profile, or accept with a reason.

## Before round 1: the profile

Core has no editorial defaults. The project's `review` object holds the limits (`minShotSeconds`, `maxShotSeconds`,
`maxStillSeconds`, `stillMotion`, `jumpCutChange`, `blackMinSeconds`, `hookSeconds`, `maxSilenceSeconds`,
`maxMusicGapSeconds`, `voiceoverMarginSeconds`, `captionLineChars`, `captionMaxLines`, `minTextSize`,
`minTextContrast`, `minSpeechCoverage`, `loudnessToleranceLU`, `severities`, `platform`). A limit left unset reports only the measured
value, as info, and `review run --summary` lists it under `checks.unsetLimits` (only the limits a check of this
timeline reads: no music, no `maxMusicGapSeconds`). `plan check` lists the same set before the build; with the
quick path write them in `plan.cut.review`. A vlog recipe sets them; otherwise
choose from the ranges below for this genre and footage, write them with `project set-data review review.json
--merge` (or `plan.cut.review`; a `setProjectProperties` patch replaces the whole review object and drops the other
limits), record each in the plan's `ranges` with its source and reason, and tell the user.

Ranges seen in other review tools (T16 §3), starting points, not defaults:

| Limit | Range | What moves it |
|---|---|---|
| `blackMinSeconds` | 0.1–0.5 s | intended dips to black (a fade-through, an end card) |
| `maxStillSeconds` (exact freeze) | 0.8–1 s | a frozen picture on video media is usually a decode or render fault |
| `maxShotSeconds` with `stillMotion` (static shot) | 3–6 s | genre: a calm daily vlog keeps long stills, a montage does not |
| `maxSilenceSeconds` (dead air, whole mix) | 2–5 s | ASMR or a calm vlog tolerates more; a gap inside speech is another measure (~0.8 s) |
| word landing (caption or title vs its word) | 0.1–0.3 s | correctness, not taste |
| `minTextContrast` (text against the picture behind it, measured by `review measure`) | 3–4.5 : 1 | WCAG 2.x: 3:1 for large text such as titles, 4.5:1 for body-size text such as captions |
| `loudnessToleranceLU` | 1–2 LU | the platform and the delivery file |
| true peak | −1 to −0.5 dBTP | lossy codecs need −1 |
| length vs plan | 2–15 % | strict for ads and fixed slots, loose for vlogs |
| rounds | 1–3 | 1 when the user is waiting and nothing blocks; up to 3 unattended (T00 §3, T16 §3) |

The platform's own numbers (length, loudness target, safe zones) come from `platforms get <id>`, never from here.

## The loop

```sh
bashcut review measure                      # a job: picture checks, plugin checks
bashcut jobs wait <job> --done --timeout 100       # returns when it ends (repeat on timedOut)
bashcut review run --summary --min-severity warning
```

Each round:

1. **Measure, then read.** `review run --summary` returns the issues and `checks`: `measured`, `stale` (an older
   revision: measure again), `notChecked` (with how to measure it), `failed` (plugin checks that timed out),
   `unreliable` (picture that barely changes: not a pass) and `unsetLimits`, plus `status`: `pass`, `fail` (errors)
   or `incomplete` (no errors, but `unsetLimits` or `notChecked` is not empty: the editorial checks did not run;
   `atExport`, loudness until the normalized draft, does not count).
   `incomplete` is never reported as passing: set the profile or say what was not checked. Errors come first. Info issues
   `voice-text-changed-*`, `captions-source-changed-*` and `beats-source-changed-*` mean a result was made from an
   older text or file: run their fix (speak, transcribe or detect again) before judging that voice, those captions
   or that beat grid.
2. **Look in batches.** `timeline sheet --cuts --text` (and `--outputs all` for safe zones), then
   `review window F --span S --step K` only at cuts in doubt: ±0.4–1.5 s at 10–12 fps, wider for dialogue cuts,
   narrower for beat cuts (T16 §3; at 30 fps about `--span 12`–`45` with `--step 3`). `ui frame F --phone` for text
   at the width a viewer sees (360–420 px, T16 §3). `review coverage` (which described shot each clip plays) and `script check` against the plan.
3. **Critic, always.** Run `review packet --point draft` and give the folder to the `bc:critic` agent (or your own
   sub-agent with `<skill_dir>/CRITIC.md` as its brief): it has none of your reasons, so it sees what a viewer
   sees. No sub-agent: audit yourself by that brief from the packet only and record `--by self`. Either way record
   the verdict with `run append audit`; a skipped G5 needs it. The critic gives its fixes as one `plan.cut` patch:
   apply it (`plan apply --patch`) without re-reading the packet. A second look only for a blocker no measurement
   proves; `review verify` proves the rest.
4. **Decide each finding** by severity: fix it (one labelled edit; the issue's `fix` when it has one), change the
   profile when the limit is wrong for this video (say so and why), accept it with a reason, or list it for the user.
5. **Verify each fix.** `review verify <id>` re-measures the issue's range and returns before and after numbers, the
   status (fixed or persisting) and a strip; `review verify all` (or `ID1,ID2`) proves a whole round in one call,
   with a strip only for what persists. Say "fixed" only with that result: "looks better" does not count (T16 §2).
   After a fix, look again only at what changed (`timeline sheet --entries c2,t1`), not the whole timeline.
6. **Log the round**: `run append round --round 1 --fixed 3 --left 2`.

Applying a `fix`: `timeline.apply` → write `fix.arguments.ops` to a file and run `timeline apply ops.json --base-rev
N --label "<the issue title>"`; `timeline.close-gap` → `timeline close-gap --at-frame <atFrame> --base-rev N`;
`review.measure` → measure, then review again; `export.start` → loudness comes from a normalized draft, last;
`selects.place` → place it or unmark it with a reason (`bc:rough-cut`); a `hint` only → do it with that area's skill,
or ask the user when it is taste. Never hide a failure with a transition or a flash (T16 §4).

| Issue | Look and measure | Fix with |
|---|---|---|
| black, frozen picture, long static shot | `ui frame F`, `review picture --from F --to G`, `review shots --summary` | `bc:beat-cut`, `bc:effects`; black: offline media or a hiding layer |
| jump cut, repeated framing, gap, cut in a word | `review window F`, `review shots` (`cut`), `timeline apply --dry-run` (`cutsInsideWord`) | `bc:rough-cut`, `bc:beat-cut` |
| cut or text off the beat or the word | `review window F`, `review sync --events cuts,text` | `bc:beat-cut`, `bc:captions-text` |
| text in a zone, too small, overlap, contrast | `ui frame F --phone`, `review layout --frame F --contrast` | `bc:captions-text` |
| no hook, slow opening | `review shots --to F`, `review layout --to F --ink`, `transcript words --to F`, the first cells of the sheet | story (`bc:edit-workflow`), `bc:captions-text` |
| dead air, music gaps, voice drowned | `review window F`, `audio mix-measure` (a job) | `bc:audio-mix` |
| loudness, true peak | the normalized draft, `platforms get <id>` | `bc:audio-mix` |
| colour jumps between clips | `color measure --by clip`, `ui frame F` | `bc:color-grade` |
| a promised shot or line missing | `review coverage`, `script check` | `bc:rough-cut`, `bc:stock-images`, ask |
| a `plan.visuals` row not built, off its word by more than 0.3 s, or over the face | `timeline get` (items at each row's `at`), `review window F`, `media subjects` | the row's building skill (`bc:visual-plan` §2) |
| a reference style drifts | `review compare --reference ID --ours ID` | `bc:style-study` |
| a plugin's issue (`source`) | that plugin's skill | its `fix` |

## Rounds and stopping

- Cap: 1–3 rounds (T00 §3, T16 §3), never more than `workflow gates` › `maxReviewRounds` (the user's setting).
- From round 2, run `review run --summary --since-rev R` (R = the revision of the previous round): it returns
  `diff {fixed, new, persisting}` and the round number. First confirm the previous fixes; then **only a new blocker
  fails the round**. New polish and taste findings are listed, not chased (T16 §2, reelmimic): rubric creep is how
  loops never end.
- Stop early when a round fixes nothing. Whatever is left goes to the user with reasons, never silently shipped.

## Accept with a reason

A warning or info that is right for this video is kept on purpose:
`review accept <id> --reason "locked-off interview, chosen" --base-rev N`. Later runs show it with the reason, leave
it out of the counts, and the export report lists it. Errors cannot be accepted: fix them, or change their severity
in `review.severities` with a reason and tell the user. `review accept <id> --remove --base-rev N` counts it again.

## The draft

Loudness and the delivered file are measured from a normalized export of this revision, so make it last, then
review once more and request G5 (`bc:edit-workflow`):

```sh
bashcut export start --preset quick-draft --name draft-v1 --include-srt --normalize-audio
bashcut export status                      # also the delivered-file checks: stream starts, drift, fps, size
bashcut review run --summary --min-severity warning
```

## Report

Report the loop in a few lines, from `run log` and the last `review run --summary`: its `status`; the audit verdicts
(and whether self-audited); rounds; what was fixed
(issue → edit → `review verify` numbers); profile changes with the reason; every accepted issue with its reason;
`needs_user`; and coverage: copy `checks.notChecked`, `failed` and `unreliable`, and say "not listened to" for sound
you could not hear. Example: "Review: 2 rounds. Closed a 1.2 s black gap at 0:41 (verified: 0 black frames), raised
the caption out of the Reels bar. Kept: the 9 s shot at 0:12 (locked-off interview). Not checked: the music under
your voice by ear; the face-overlap check (no provider)." Log it: `run append measured --measured … --not-measured …`.