# 1.12.0 — `validate --fix`: the umbrella auto-fixer

Minor release. One new verb (a flag on `validate`), one tiny refactor, zero
changes to existing behavior. It is the capstone of the validate→fixer family:
where v1.8.0 `validate` *reports* fixable problems and v1.9–1.11 added the
individual fixers, `validate --fix` *orchestrates* them.

## The problem it solves

`validate` finds problems and names the fixer for each (`gc`, `init-meta`,
`register`, `sync-timelines`). Running them by hand means knowing the right order
— and the order matters: `register` needs the sidecar `init-meta` creates, and
`sync-timelines` must run *after* `gc` rewrites the root or the mirror re-diverges
instantly. `validate --fix` runs the whole chain correctly, or previews exactly
what it would do.

## Highlights

- 🩹 **`capcut-david validate <project> --fix`** — previews the aggregated fix
  plan (which fixer handles which finding, in dependency order). **Zero writes.**
- ✍️ **`--fix --apply`** — actually applies the fixers, then **re-validates** and
  reports the residual.
- 🔒 **Dry-run by default.** Unlike the standalone `gc`/`sync-timelines`
  (apply-by-default), the umbrella requires `--apply` to write — because it
  aggregates a **destructive** `gc` removal across many findings in one pass.
  `--fix --apply --dry-run` is rejected as contradictory.
- 🧭 **Fixed dependency order:** `gc → init-meta → register → sync-timelines`
  (sync strictly last). Hardcoded, not encounter-order.
- 🎯 **Selective fixing reuses `--id`/`--skip`.** `--fix --id timelines.divergence`
  fixes only that; `--skip materials.orphan_text` excludes it (but `gc` still runs
  for `orphan_media` — the filter is a per-finding-id union).
- 🛡️ **Refuses on a broken draft.** A `dangling_ref`/`duplicate_id` draft blocks
  `--apply` (exit 2, zero writes) — a destructive `gc` on an inconsistent draft is
  unsafe. Dry-run still shows the plan, flagged `blocked`.
- 🔓 **Conditional CapCut guard.** Read-only `validate` and dry-run `--fix` run
  with CapCut open; only `--apply` requires CapCut closed (`--force` bypasses).
- 🧪 **399 tests** (+24). Typecheck clean; `validate-fix.ts` lint-clean. One
  refactor: `applyInitMeta` extracted from `init-meta` (byte-identical).

## Usage

```
$ capcut-david validate my-draft --fix                  # preview the plan (no writes)
$ capcut-david validate my-draft --fix --apply          # apply, then re-validate
$ capcut-david validate my-draft --fix --id meta.missing # fix only one finding type
$ capcut-david validate my-draft --fix --skip materials.orphan_media
```

The whole repair flow in one command (vs the manual chain):

```
$ capcut-david validate my-draft           # errors/warnings: orphans, meta.missing, divergence…
$ capcut-david validate my-draft --fix     # preview: gc → init-meta → register → sync-timelines
$ capcut-david validate my-draft --fix --apply   # apply all, in order; re-validate; exit on residual
```

JSON envelope — dry-run (extends `capcut-david/validate@1` with an additive
`fix` key; a plain `validate` envelope is unchanged):

```json
{
  "schema": "capcut-david/validate@1",
  "ok": true,
  "project": "C:\\...\\my-draft",
  "draft_file": "C:\\...\\my-draft\\draft_content.json",
  "summary": { "errors": 1, "warnings": 1, "info": 2, "checks_run": 11, "checks_skipped": 4 },
  "findings": [ "… the full validate finding list …" ],
  "fix": {
    "applied": false,
    "blocked": false,
    "blocked_reason": null,
    "note": null,
    "order": ["gc", "init-meta", "register", "sync-timelines"],
    "plan": [
      { "fixer": "gc", "finding_ids": ["materials.orphan_text", "materials.orphan_media"],
        "action": "remove 3 orphan material(s)", "destructive": true, "blocked": false },
      { "fixer": "init-meta", "finding_ids": ["meta.missing"],
        "action": "create the missing draft_meta_info.json sidecar", "destructive": false, "blocked": false }
    ],
    "unfixable": [],
    "excluded": []
  }
}
```

After `--apply`, `fix.applied` is `true`, `fix.plan` becomes `fix.results[]`
(per-fixer `wrote`/`skipped`/`error` + `.bak` paths), and `fix.residual` holds the
re-validate outcome.

**Exit codes:** dry-run → always `0`; `--apply` → `0` (residual clean) or `2`
(residual problems); blocking-error refusal → `2`; `sync-timelines` failure or any
tool error → `1`. **Orphan findings are `info`-severity**, so the exit code is not
the success signal for `gc` — `fix.results[].wrote` is.

## Migration

**None.** `validate --fix` is additive; a plain `validate` envelope is unchanged
(the `fix` key appears only with `--fix`). The `applyInitMeta` extraction is
byte-identical, so `init-meta` and `psycho-build` are unaffected.

## Compatibility

- CapCut ≥ 5.x desktop (Windows + macOS), JianYing 6+ unsupported — unchanged.
- Node `>= 18` — unchanged.
- Runtime dependencies: zero — unchanged.

## Roadmap (1.x — non-binding)

The validate→fixer family is now complete and unified under `validate --fix`.
Remaining backlog: `capcut-david query` (catalogue lookup); the restyle "System"
font-dropdown cosmetic quirk.
