davinci-resolve-mcp 2.135.0 → 2.136.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +12 -4
- package/CHANGELOG.md +487 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/README.md +52 -41
- package/docs/SKILL.md +1 -1
- package/docs/kernels/README.md +1 -1
- package/docs/kernels/audio-fairlight-kernel.md +1 -1
- package/docs/kernels/color-grade-kernel.md +1 -1
- package/docs/kernels/fusion-composition-kernel.md +1 -1
- package/docs/kernels/media-pool-ingest-kernel.md +1 -1
- package/docs/kernels/render-deliver-kernel.md +1 -1
- package/docs/kernels/timeline-conform-interchange-kernel.md +1 -1
- package/docs/kernels/timeline-edit-kernel.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/scripts/agent-rules/README.md +6 -4
- package/scripts/agent-rules/sync_portable_assets.py +109 -0
- package/scripts/audit_api_parity.py +4 -4
- package/scripts/bisect_headless_hang.py +1 -1
- package/scripts/doctor.py +2 -2
- package/scripts/gen_api_limitations.py +2 -2
- package/scripts/mode_matrix.py +3 -3
- package/scripts/mode_matrix_worker.py +1 -1
- package/scripts/pixel_equality.py +2 -2
- package/scripts/render_stress.py +2 -2
- package/scripts/resolve_bridge_probe.py +2 -2
- package/scripts/resolve_capability_probe.py +1 -1
- package/scripts/roundtrip_matrix.py +2 -2
- package/scripts/roundtrip_rich.py +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +3 -3
- package/src/utils/knowledge.py +4 -18
package/docs/README.md
CHANGED
|
@@ -41,57 +41,63 @@ ignored scratch folders such as `docs/_scratch/`.
|
|
|
41
41
|
- [Project Lifecycle](kernels/project-lifecycle-kernel.md)
|
|
42
42
|
- [Extension Authoring](kernels/extension-authoring-kernel.md)
|
|
43
43
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
Per-domain skills in `.
|
|
47
|
-
advanced tools automatically when an agent works in that domain.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- `resolve-
|
|
56
|
-
- `resolve-
|
|
57
|
-
- `resolve-
|
|
58
|
-
- `resolve-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
## Portable Agent Skills
|
|
45
|
+
|
|
46
|
+
Per-domain skills in `.agents/skills/` route craft ↔ live tools ↔ offline
|
|
47
|
+
advanced tools automatically when an agent works in that domain. This is the
|
|
48
|
+
canonical, client-neutral source. Codex discovers it directly; Claude Code loads
|
|
49
|
+
`.claude/skills/` copies that are CONTENT-COMPLETE and byte-identical to the
|
|
50
|
+
canonical files — Claude routes on the frontmatter description at selection
|
|
51
|
+
time, so a pointer stub would degrade skill routing. Edit either copy, then run
|
|
52
|
+
`scripts/agent-rules/sync_portable_assets.py`; the parity guard
|
|
53
|
+
(`tests/test_portable_asset_parity.py`) fails the suite on drift.
|
|
54
|
+
|
|
55
|
+
- `resolve-mcp` — orientation/index: the map to the domain skills below (self-trigger; not an auto-loader)
|
|
56
|
+
- `resolve-color` — grading, looks, shot match, LUT/CDL/DRX
|
|
57
|
+
- `resolve-edit` — cutting, ranges, variants, changelist
|
|
58
|
+
- `resolve-conform` — conform, relink, finishing QC, grade tracing
|
|
59
|
+
- `resolve-delivery` — render, deliverable QC, media/provenance
|
|
60
|
+
- `resolve-fusion` — Fusion comps (titles, motion graphics, VFX)
|
|
61
|
+
- `resolve-audio` — audio/Fairlight tracks, buses, loudness, sync
|
|
62
|
+
- `resolve-media-pool` — media pool ingest, organize, multicam
|
|
63
|
+
- `resolve-media-analysis` — source-safe media intelligence
|
|
64
|
+
|
|
65
|
+
Each canonical skill is a directory containing `SKILL.md`. The Claude adapter
|
|
66
|
+
uses the same directory shape because Claude Code does not discover loose files.
|
|
63
67
|
|
|
64
68
|
Two end-to-end assembly recipes sit alongside the domain skills. Where a domain
|
|
65
69
|
skill routes, these two walk a whole job:
|
|
66
70
|
|
|
67
|
-
- `resolve-rough-cut`
|
|
71
|
+
- `resolve-rough-cut` — **additive**:
|
|
68
72
|
select shots from a folder of many clips into an assembled timeline.
|
|
69
|
-
- `resolve-tighten-recording`
|
|
73
|
+
- `resolve-tighten-recording`
|
|
70
74
|
— **subtractive**: remove dead air from one long single-take recording.
|
|
71
75
|
|
|
72
76
|
Three more sit outside the domain routing:
|
|
73
77
|
|
|
74
|
-
- `house-style`
|
|
75
|
-
corrections, so the same note is not given twice.
|
|
78
|
+
- `house-style` — accumulated editorial
|
|
79
|
+
corrections, so the same note is not given twice. Append to it
|
|
76
80
|
when an editorial decision is corrected.
|
|
77
|
-
- `resolve-session`
|
|
81
|
+
- `resolve-session` — `/resolve-session`
|
|
78
82
|
connects, confirms edition and bridge, and reports project/timeline/pool state.
|
|
79
|
-
- `release-check`
|
|
83
|
+
- `release-check` — `/release-check`
|
|
80
84
|
walks a version bump using [docs/process/release-process.md](process/release-process.md)
|
|
81
85
|
as the sole source; the skill wraps that doc, it does not duplicate it.
|
|
82
86
|
|
|
83
87
|
The offline half of every one is the advanced server; see
|
|
84
88
|
[Advanced Server](../resolve-advanced/README.md).
|
|
85
89
|
|
|
86
|
-
##
|
|
90
|
+
## Portable Hooks and Reviewers
|
|
87
91
|
|
|
88
|
-
Four hooks live in `.
|
|
89
|
-
`AGENTS.md` states in prose, and two `PostToolUse` checks that
|
|
90
|
-
engineering drift
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
Four canonical hooks live in `.agents/hooks/` — two `PreToolUse` guards
|
|
93
|
+
enforcing rules `AGENTS.md` states in prose, and two `PostToolUse` checks that
|
|
94
|
+
surface engineering drift immediately after the edit that caused it. Thin
|
|
95
|
+
wrappers in `.claude/hooks/` and `.codex/hooks/` translate only the host
|
|
96
|
+
protocol; policy stays in the canonical scripts.
|
|
97
|
+
|
|
98
|
+
Codex wiring is committed in `.codex/hooks.json`. Because project hooks execute
|
|
99
|
+
commands, review and trust that file through Codex's `/hooks` UI. Claude Code
|
|
100
|
+
users opt in by adding this block to `.claude/settings.local.json`:
|
|
95
101
|
|
|
96
102
|
```json
|
|
97
103
|
{
|
|
@@ -139,18 +145,18 @@ yours):
|
|
|
139
145
|
}
|
|
140
146
|
```
|
|
141
147
|
|
|
142
|
-
Hooks are read at session start, so restart
|
|
143
|
-
four are:
|
|
148
|
+
Hooks are read at session start, so restart the client after enabling them. The
|
|
149
|
+
four canonical policies are:
|
|
144
150
|
|
|
145
|
-
- `.
|
|
151
|
+
- `.agents/hooks/frame_verification_guard.py` — denies grade-applying actions on
|
|
146
152
|
`timeline_item_color` until the session has actually looked at a
|
|
147
153
|
Resolve-rendered frame, and asks before whole-grade artifacts
|
|
148
154
|
(`safe_copy_grade`, `bulk_match_to_hero`) overwrite hand-work. `dry_run`
|
|
149
155
|
passes through untouched.
|
|
150
|
-
- `.
|
|
156
|
+
- `.agents/hooks/source_media_guard.py` — denies shell commands that write,
|
|
151
157
|
move, or delete source media outside a scratch root. Reads (`ffprobe`, and
|
|
152
158
|
`ffmpeg` writing into scratch) pass.
|
|
153
|
-
- `.
|
|
159
|
+
- `.agents/hooks/agent_rules_drift_check.py` — after an edit to anything
|
|
154
160
|
`generate.mjs` reads (`docs/SKILL.md`, `docs/kernels/README.md`,
|
|
155
161
|
`resolve-advanced/README.md`, and `generate.mjs` itself, which carries the
|
|
156
162
|
DOMAINS manifest inline) or to `AGENTS.md`, which it writes, runs
|
|
@@ -160,7 +166,7 @@ four are:
|
|
|
160
166
|
blocks. Skips quietly if `node` isn't on `PATH`. **If `generate.mjs` grows a
|
|
161
167
|
new input, add it to `SOURCE_PATHS` in the hook** — an unwatched input is a
|
|
162
168
|
silent hook on exactly the edit it exists to catch.
|
|
163
|
-
- `.
|
|
169
|
+
- `.agents/hooks/run_matching_test.py` — after an edit to `src/<module>.py`,
|
|
164
170
|
runs the matching `tests/test_<module>.py` if one exists, using the project
|
|
165
171
|
venv (`venv/bin/python`) so `pytest` is actually importable. Informational
|
|
166
172
|
only; skips quietly if there's no matching test file or no working `pytest`.
|
|
@@ -169,8 +175,13 @@ four are:
|
|
|
169
175
|
for `src/server.py` or `src/granular/common.py`. Silence means "no matching
|
|
170
176
|
test file", not "this edit is fine".
|
|
171
177
|
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
Codex does not currently support an interactive `ask` result from `PreToolUse`.
|
|
179
|
+
For the two whole-grade actions, its adapter adds a warning while the MCP
|
|
180
|
+
server's confirmation-token gate remains authoritative. Hard denials, including
|
|
181
|
+
missing frame evidence and source-media writes, block in both clients.
|
|
182
|
+
|
|
183
|
+
Three canonical reviewer roles live in `.agents/roles/`, with native Codex
|
|
184
|
+
definitions under `.codex/agents/` and Claude adapters under `.claude/agents/`:
|
|
174
185
|
|
|
175
186
|
- `cut-reviewer` — screens an assembled timeline from its frames and reports on
|
|
176
187
|
pacing, shot order, continuity, and coverage gaps.
|
package/docs/SKILL.md
CHANGED
|
@@ -207,7 +207,7 @@ Operating rules an agent must know:
|
|
|
207
207
|
|
|
208
208
|
Per-domain depth for both servers lives in the kernels (`docs/kernels/`), each of
|
|
209
209
|
which carries an "Advanced (offline) server" section where an offline counterpart
|
|
210
|
-
exists.
|
|
210
|
+
exists. Portable skills in `.agents/skills/` (`resolve-color`, `resolve-edit`,
|
|
211
211
|
`resolve-conform`, `resolve-delivery`, `resolve-media-analysis`) route
|
|
212
212
|
craft ↔ live ↔ offline automatically when working in that domain.
|
|
213
213
|
|
package/docs/kernels/README.md
CHANGED
|
@@ -38,7 +38,7 @@ two:
|
|
|
38
38
|
- Timeline Edit → `editorial` (interchange + changelist), `drt`
|
|
39
39
|
|
|
40
40
|
Per-domain Claude Code skills route craft ↔ live ↔ offline automatically and live
|
|
41
|
-
in `.
|
|
41
|
+
in `.agents/skills/`: `resolve-color`, `resolve-edit`, `resolve-conform`,
|
|
42
42
|
`resolve-delivery` (plus `resolve-media-analysis`). `docs/SKILL.md` is the
|
|
43
43
|
top-level brief for both servers.
|
|
44
44
|
|
|
@@ -121,7 +121,7 @@ patches audio **offline, no Resolve running**:
|
|
|
121
121
|
|
|
122
122
|
Rule of thumb: plan/measure offline, apply mix/track changes live; use
|
|
123
123
|
`fairlight` for bus work the scripting API can't reach. See the `resolve-audio`
|
|
124
|
-
skill (`.
|
|
124
|
+
skill (`.agents/skills/resolve-audio/SKILL.md`) and the `/audio_workflow` prompt.
|
|
125
125
|
|
|
126
126
|
## Live Probe
|
|
127
127
|
|
|
@@ -156,7 +156,7 @@ Cross-server rules an agent must know:
|
|
|
156
156
|
- **Deps.** The grading catalog needs `sharp`; call the advanced `capabilities`
|
|
157
157
|
tool for live status and install hints.
|
|
158
158
|
|
|
159
|
-
See the `resolve-color` skill (`.
|
|
159
|
+
See the `resolve-color` skill (`.agents/skills/resolve-color/SKILL.md`) for the
|
|
160
160
|
craft ↔ live ↔ offline routing and the frame-first rule.
|
|
161
161
|
|
|
162
162
|
## Live Probe
|
|
@@ -119,7 +119,7 @@ running**, via the `fusion` tool:
|
|
|
119
119
|
Rule of thumb: author and verify the comp offline, then apply it live with
|
|
120
120
|
`fusion_comp` `safe_add_tool` → `safe_set_inputs` → `safe_connect_tools` (the
|
|
121
121
|
`to_api_calls` output maps directly onto those). See the `resolve-fusion` skill
|
|
122
|
-
(`.
|
|
122
|
+
(`.agents/skills/resolve-fusion/SKILL.md`) and the `/fusion_workflow` prompt.
|
|
123
123
|
|
|
124
124
|
## Live Probe
|
|
125
125
|
|
|
@@ -137,7 +137,7 @@ actions):
|
|
|
137
137
|
Rule of thumb: verify and inventory the card offline *before* importing, then
|
|
138
138
|
import/organize live. `media` also serves the delivery side (see the
|
|
139
139
|
`resolve-delivery` skill). See the `resolve-media-pool` skill
|
|
140
|
-
(`.
|
|
140
|
+
(`.agents/skills/resolve-media-pool/SKILL.md`) and the `/media_pool_workflow` prompt. Never
|
|
141
141
|
rename or derive camera originals without explicit approval.
|
|
142
142
|
|
|
143
143
|
## Live Evidence
|
|
@@ -135,7 +135,7 @@ Rules an agent must know:
|
|
|
135
135
|
- **Deps.** `deliverable`/`media` QC needs **ffmpeg + ffprobe on PATH** (GPL, not
|
|
136
136
|
bundled) — call the advanced `capabilities` tool for status + install hints.
|
|
137
137
|
|
|
138
|
-
See the `resolve-delivery` skill (`.
|
|
138
|
+
See the `resolve-delivery` skill (`.agents/skills/resolve-delivery/SKILL.md`) for the
|
|
139
139
|
craft ↔ live ↔ offline routing.
|
|
140
140
|
|
|
141
141
|
## Live Evidence
|
|
@@ -160,7 +160,7 @@ Gotchas the live path shares:
|
|
|
160
160
|
- **Deps.** `better-sqlite3` gates lineage/reverse/DB; `sharp`/ffmpeg gate frame
|
|
161
161
|
compare — call the advanced `capabilities` tool.
|
|
162
162
|
|
|
163
|
-
See the `resolve-conform` skill (`.
|
|
163
|
+
See the `resolve-conform` skill (`.agents/skills/resolve-conform/SKILL.md`) for the
|
|
164
164
|
craft ↔ live ↔ offline routing.
|
|
165
165
|
|
|
166
166
|
## Live Probe
|
|
@@ -196,7 +196,7 @@ Use these to answer "what changed between v3 and v4" or to hand a conform an
|
|
|
196
196
|
accurate change list without opening either timeline. For conforming/relinking
|
|
197
197
|
that change list, see the Timeline Conform / Interchange kernel and the
|
|
198
198
|
`resolve-conform` skill; for the edit ↔ offline routing, see the `resolve-edit`
|
|
199
|
-
skill (`.
|
|
199
|
+
skill (`.agents/skills/resolve-edit/SKILL.md`).
|
|
200
200
|
|
|
201
201
|
## Development Guardrails
|
|
202
202
|
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.
|
|
40
|
+
VERSION = "2.136.1"
|
|
41
41
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
42
42
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
43
43
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -24,7 +24,8 @@ cannot drift.
|
|
|
24
24
|
| Platform | File(s) | Trigger | Source |
|
|
25
25
|
|---|---|---|---|
|
|
26
26
|
| Codex / OpenCode / Zed / others | `AGENTS.md` (`## Domain Routing` block) | always-on | generated block |
|
|
27
|
-
|
|
|
27
|
+
| Codex and compatible agents | `.agents/skills/<name>/SKILL.md` | semantic (description) | hand-authored canonical skill |
|
|
28
|
+
| Claude Code | `.claude/skills/<name>/SKILL.md` | semantic (description) | thin adapter to canonical skill |
|
|
28
29
|
| Cursor | `.cursor/rules/*.mdc` | `alwaysApply` repo rule + per-domain `description` (agent-requested) | generated |
|
|
29
30
|
| VS Code / Copilot | `.github/copilot-instructions.md` + `.github/instructions/*.instructions.md` | always-on + `applyTo` | generated |
|
|
30
31
|
| Windsurf | `.windsurf/rules/*.md` + `.windsurfrules` | rules dir + legacy flat | generated |
|
|
@@ -33,8 +34,9 @@ cannot drift.
|
|
|
33
34
|
| Continue | `.continue/rules/resolve-mcp.md` | always-on | generated |
|
|
34
35
|
| Claude Desktop | — (chat client, no repo rules) | — | MCP prompts only |
|
|
35
36
|
|
|
36
|
-
`.
|
|
37
|
-
|
|
37
|
+
`.agents/skills/*` stays hand-authored as the canonical rich skill source.
|
|
38
|
+
`.claude/skills/*` contains only compatibility adapters. Each skill is a
|
|
39
|
+
**directory** holding a `SKILL.md` named for its frontmatter `name`;
|
|
38
40
|
a loose `.md` at the top level of `.claude/skills/` is never discovered and
|
|
39
41
|
fails silently. Every
|
|
40
42
|
other file is generated. `AGENTS.md` remains the universal backstop for any
|
|
@@ -65,7 +67,7 @@ node scripts/agent-rules/generate.mjs --check # exit 1 if anything is stale (C
|
|
|
65
67
|
|
|
66
68
|
- **New domain:** add an entry to `DOMAINS` in `generate.mjs`, add a matching
|
|
67
69
|
`@mcp.prompt` in `src/server.py`, and (optionally) a rich
|
|
68
|
-
`.
|
|
70
|
+
`.agents/skills/<name>/SKILL.md` and add/update its client adapter. Regenerate.
|
|
69
71
|
- **New platform:** add an `emit(...)` for its convention in `generate.mjs`,
|
|
70
72
|
reusing `domainBody(d)` / `repoHygiene`. Regenerate. Do not hand-edit generated
|
|
71
73
|
files — the drift guard will fail.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Sync the portable agent assets (`.agents/`) with their Claude adapters.
|
|
3
|
+
|
|
4
|
+
The layout contract (adapted from PR #173):
|
|
5
|
+
|
|
6
|
+
- `.agents/skills/*/SKILL.md` is the canonical, host-neutral skill corpus —
|
|
7
|
+
the `knowledge` MCP tool serves it, and non-Claude hosts read it directly.
|
|
8
|
+
- `.claude/skills/*/SKILL.md` is a CONTENT-COMPLETE copy, byte-identical to
|
|
9
|
+
the canonical file. Claude Code routes on the frontmatter description at
|
|
10
|
+
selection time, so the adapter must carry the full text — a pointer stub
|
|
11
|
+
would cost a read per invocation and hide the trigger phrasing.
|
|
12
|
+
- `.agents/roles/*.md` is the body (frontmatter stripped) of the matching
|
|
13
|
+
`.claude/agents/*.md`. The Claude file keeps its frontmatter — name,
|
|
14
|
+
description, tools, and the deliberate `model:` pin — which host-neutral
|
|
15
|
+
role files cannot carry.
|
|
16
|
+
- `.agents/hooks/*.py` is canonical executable logic; `.claude/hooks` and
|
|
17
|
+
`.codex/hooks` are thin runpy shims (execution-only surfaces need no
|
|
18
|
+
content duplication).
|
|
19
|
+
|
|
20
|
+
Edit EITHER side of a skill (they are identical); edit `.claude/agents` for
|
|
21
|
+
roles. Then run this script to restore the invariant:
|
|
22
|
+
|
|
23
|
+
venv/bin/python scripts/agent-rules/sync_portable_assets.py # write
|
|
24
|
+
venv/bin/python scripts/agent-rules/sync_portable_assets.py --check # verify only
|
|
25
|
+
|
|
26
|
+
`tests/test_portable_asset_parity.py` fails the suite when the invariant is
|
|
27
|
+
broken, so drift cannot land.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import re
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
REPO = Path(__file__).resolve().parents[2]
|
|
38
|
+
CLAUDE_SKILLS = REPO / ".claude" / "skills"
|
|
39
|
+
AGENT_SKILLS = REPO / ".agents" / "skills"
|
|
40
|
+
CLAUDE_AGENTS = REPO / ".claude" / "agents"
|
|
41
|
+
AGENT_ROLES = REPO / ".agents" / "roles"
|
|
42
|
+
|
|
43
|
+
_FRONTMATTER_RE = re.compile(r"^---\n.*?\n---\n\n?", re.S)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def strip_frontmatter(text: str) -> str:
|
|
47
|
+
m = _FRONTMATTER_RE.match(text)
|
|
48
|
+
return text[m.end():] if m else text
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def skill_pairs():
|
|
52
|
+
names = sorted(
|
|
53
|
+
{p.parent.name for p in CLAUDE_SKILLS.glob("*/SKILL.md")}
|
|
54
|
+
| {p.parent.name for p in AGENT_SKILLS.glob("*/SKILL.md")}
|
|
55
|
+
)
|
|
56
|
+
for name in names:
|
|
57
|
+
yield name, CLAUDE_SKILLS / name / "SKILL.md", AGENT_SKILLS / name / "SKILL.md"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def role_pairs():
|
|
61
|
+
names = sorted(
|
|
62
|
+
{p.stem for p in CLAUDE_AGENTS.glob("*.md")} | {p.stem for p in AGENT_ROLES.glob("*.md")}
|
|
63
|
+
)
|
|
64
|
+
for name in names:
|
|
65
|
+
yield name, CLAUDE_AGENTS / f"{name}.md", AGENT_ROLES / f"{name}.md"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run(check_only: bool) -> int:
|
|
69
|
+
problems: list[str] = []
|
|
70
|
+
for name, claude_p, agent_p in skill_pairs():
|
|
71
|
+
if not claude_p.exists():
|
|
72
|
+
problems.append(f"skill '{name}': canonical exists but .claude adapter is missing — it would never load in Claude Code")
|
|
73
|
+
if not check_only and agent_p.exists():
|
|
74
|
+
claude_p.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
claude_p.write_text(agent_p.read_text(encoding="utf-8"), encoding="utf-8")
|
|
76
|
+
continue
|
|
77
|
+
src = claude_p.read_text(encoding="utf-8")
|
|
78
|
+
if not agent_p.exists() or agent_p.read_text(encoding="utf-8") != src:
|
|
79
|
+
problems.append(f"skill '{name}': .agents copy {'missing' if not agent_p.exists() else 'differs'}")
|
|
80
|
+
if not check_only:
|
|
81
|
+
agent_p.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
agent_p.write_text(src, encoding="utf-8")
|
|
83
|
+
for name, claude_p, role_p in role_pairs():
|
|
84
|
+
if not claude_p.exists():
|
|
85
|
+
problems.append(f"role '{name}': .agents/roles exists but .claude/agents is missing — restore the frontmatter file by hand (it carries tools/model pins this script cannot invent)")
|
|
86
|
+
continue
|
|
87
|
+
body = strip_frontmatter(claude_p.read_text(encoding="utf-8"))
|
|
88
|
+
if not role_p.exists() or role_p.read_text(encoding="utf-8") != body:
|
|
89
|
+
problems.append(f"role '{name}': .agents/roles {'missing' if not role_p.exists() else 'differs from the .claude/agents body'}")
|
|
90
|
+
if not check_only:
|
|
91
|
+
role_p.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
role_p.write_text(body, encoding="utf-8")
|
|
93
|
+
if problems:
|
|
94
|
+
for p in problems:
|
|
95
|
+
print(("DRIFT: " if check_only else "SYNCED: ") + p)
|
|
96
|
+
return 1 if check_only else 0
|
|
97
|
+
print("portable assets in sync")
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main() -> int:
|
|
102
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
103
|
+
ap.add_argument("--check", action="store_true", help="verify only; non-zero exit on drift")
|
|
104
|
+
args = ap.parse_args()
|
|
105
|
+
return run(check_only=args.check)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
sys.exit(main())
|
|
@@ -49,7 +49,7 @@ def parse_documented_methods(docs_path: Path) -> Dict[str, Set[str]]:
|
|
|
49
49
|
"""
|
|
50
50
|
classes: Dict[str, Set[str]] = {}
|
|
51
51
|
current_class: str | None = None
|
|
52
|
-
text = docs_path.read_text()
|
|
52
|
+
text = docs_path.read_text(encoding="utf-8")
|
|
53
53
|
# Truncate at the first deprecation marker
|
|
54
54
|
for marker in ("\nDeprecated Resolve API Functions",
|
|
55
55
|
"\nUnsupported Resolve API Functions"):
|
|
@@ -86,7 +86,7 @@ def collect_source_text() -> str:
|
|
|
86
86
|
if "__pycache__" in py_path.parts:
|
|
87
87
|
continue
|
|
88
88
|
try:
|
|
89
|
-
parts.append(py_path.read_text())
|
|
89
|
+
parts.append(py_path.read_text(encoding="utf-8"))
|
|
90
90
|
except (OSError, UnicodeDecodeError):
|
|
91
91
|
continue
|
|
92
92
|
return "\n".join(parts)
|
|
@@ -100,7 +100,7 @@ def find_broken_api_imports() -> List[Tuple[Path, int, str]]:
|
|
|
100
100
|
if "__pycache__" in py_path.parts:
|
|
101
101
|
continue
|
|
102
102
|
try:
|
|
103
|
-
for i, line in enumerate(py_path.read_text().splitlines(), 1):
|
|
103
|
+
for i, line in enumerate(py_path.read_text(encoding="utf-8").splitlines(), 1):
|
|
104
104
|
if pattern.search(line):
|
|
105
105
|
hits.append((py_path.relative_to(REPO_ROOT), i, line.strip()))
|
|
106
106
|
except (OSError, UnicodeDecodeError):
|
|
@@ -199,7 +199,7 @@ def find_undocumented_method_wrappers(
|
|
|
199
199
|
if "__pycache__" in py_path.parts:
|
|
200
200
|
continue
|
|
201
201
|
try:
|
|
202
|
-
for i, line in enumerate(py_path.read_text().splitlines(), 1):
|
|
202
|
+
for i, line in enumerate(py_path.read_text(encoding="utf-8").splitlines(), 1):
|
|
203
203
|
for m in call_pattern.finditer(line):
|
|
204
204
|
name = m.group(1)
|
|
205
205
|
if name in documented or name in seen:
|
package/scripts/doctor.py
CHANGED
|
@@ -240,7 +240,7 @@ def _normalize_separators(text: str) -> str:
|
|
|
240
240
|
def file_contains(path: Path, needles: list[str]) -> tuple[bool, str]:
|
|
241
241
|
if not path.exists():
|
|
242
242
|
return False, "missing"
|
|
243
|
-
text = _normalize_separators(path.read_text(errors="replace"))
|
|
243
|
+
text = _normalize_separators(path.read_text(errors="replace", encoding="utf-8"))
|
|
244
244
|
missing = [needle for needle in needles if _normalize_separators(needle) not in text]
|
|
245
245
|
if missing:
|
|
246
246
|
return False, "missing: " + ", ".join(missing)
|
|
@@ -250,7 +250,7 @@ def file_contains(path: Path, needles: list[str]) -> tuple[bool, str]:
|
|
|
250
250
|
def version_from_server() -> str:
|
|
251
251
|
if not SERVER.exists():
|
|
252
252
|
return "unknown"
|
|
253
|
-
match = re.search(r'^VERSION\s*=\s*"([^"]+)"', SERVER.read_text(errors="replace"), re.M)
|
|
253
|
+
match = re.search(r'^VERSION\s*=\s*"([^"]+)"', SERVER.read_text(errors="replace", encoding="utf-8"), re.M)
|
|
254
254
|
return match.group(1) if match else "unknown"
|
|
255
255
|
|
|
256
256
|
|
|
@@ -139,7 +139,7 @@ def main(argv: list[str]) -> int:
|
|
|
139
139
|
args = _parse_args(argv)
|
|
140
140
|
content = render()
|
|
141
141
|
if args.check:
|
|
142
|
-
current = DOC_PATH.read_text() if DOC_PATH.exists() else ""
|
|
142
|
+
current = DOC_PATH.read_text(encoding="utf-8") if DOC_PATH.exists() else ""
|
|
143
143
|
if current != content:
|
|
144
144
|
print(
|
|
145
145
|
f"STALE: {DOC_PATH.relative_to(REPO_ROOT)} is out of date.\n"
|
|
@@ -149,7 +149,7 @@ def main(argv: list[str]) -> int:
|
|
|
149
149
|
return 1
|
|
150
150
|
print(f"OK: {DOC_PATH.relative_to(REPO_ROOT)} is up to date.")
|
|
151
151
|
return 0
|
|
152
|
-
DOC_PATH.write_text(content)
|
|
152
|
+
DOC_PATH.write_text(content, encoding="utf-8")
|
|
153
153
|
print(f"Wrote {DOC_PATH.relative_to(REPO_ROOT)}")
|
|
154
154
|
return 0
|
|
155
155
|
|
package/scripts/mode_matrix.py
CHANGED
|
@@ -162,7 +162,7 @@ def completed_probes(path: Path) -> Dict[str, Dict[str, Any]]:
|
|
|
162
162
|
results: Dict[str, Dict[str, Any]] = {}
|
|
163
163
|
if not path.exists():
|
|
164
164
|
return results
|
|
165
|
-
for line in path.read_text(errors="replace").splitlines():
|
|
165
|
+
for line in path.read_text(errors="replace", encoding="utf-8").splitlines():
|
|
166
166
|
line = line.strip()
|
|
167
167
|
if not line:
|
|
168
168
|
continue
|
|
@@ -183,7 +183,7 @@ def last_phase(path: Path) -> Optional[Dict[str, Any]]:
|
|
|
183
183
|
if not path.exists():
|
|
184
184
|
return None
|
|
185
185
|
marker = None
|
|
186
|
-
for line in path.read_text(errors="replace").splitlines():
|
|
186
|
+
for line in path.read_text(errors="replace", encoding="utf-8").splitlines():
|
|
187
187
|
line = line.strip()
|
|
188
188
|
if not line:
|
|
189
189
|
continue
|
|
@@ -197,7 +197,7 @@ def last_phase(path: Path) -> Optional[Dict[str, Any]]:
|
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
def append(path: Path, record: Dict[str, Any]) -> None:
|
|
200
|
-
with path.open("a") as handle:
|
|
200
|
+
with path.open("a", encoding="utf-8") as handle:
|
|
201
201
|
handle.write(json.dumps(record, default=str) + "\n")
|
|
202
202
|
|
|
203
203
|
|
|
@@ -177,7 +177,7 @@ def main() -> int:
|
|
|
177
177
|
wanted = set(args.probes)
|
|
178
178
|
probes = [p for p in CATALOGUE if p.name in wanted]
|
|
179
179
|
|
|
180
|
-
with args.out.open("a") as handle:
|
|
180
|
+
with args.out.open("a", encoding="utf-8") as handle:
|
|
181
181
|
try:
|
|
182
182
|
build_fixture(ctx)
|
|
183
183
|
except Exception as exc:
|
|
@@ -103,7 +103,7 @@ def build_assets() -> None:
|
|
|
103
103
|
for g in (0.0, 1.0):
|
|
104
104
|
for r in (0.0, 1.0):
|
|
105
105
|
lines.append(f"{min(b + 0.15, 1.0):.6f} {g:.6f} {min(r + 0.15, 1.0):.6f}")
|
|
106
|
-
LUT.write_text("\n".join(lines) + "\n")
|
|
106
|
+
LUT.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
def frame_hashes(path: Path) -> List[str]:
|
|
@@ -338,7 +338,7 @@ def run(mode: str, out: Path) -> int:
|
|
|
338
338
|
|
|
339
339
|
|
|
340
340
|
def compare(gui_path: Path, headless_path: Path, out: Optional[Path]) -> int:
|
|
341
|
-
gui, headless = json.loads(gui_path.read_text()), json.loads(headless_path.read_text())
|
|
341
|
+
gui, headless = json.loads(gui_path.read_text(encoding="utf-8")), json.loads(headless_path.read_text(encoding="utf-8"))
|
|
342
342
|
if gui["metadata"]["headless"] is not False or headless["metadata"]["headless"] is not True:
|
|
343
343
|
print("REFUSE: mode metadata does not match the arguments.", file=sys.stderr)
|
|
344
344
|
return 1
|
package/scripts/render_stress.py
CHANGED
|
@@ -161,7 +161,7 @@ class CrashWatch:
|
|
|
161
161
|
self.offset = size # rotation/truncation resets the mark
|
|
162
162
|
return None
|
|
163
163
|
try:
|
|
164
|
-
with CRASH_ARCHIVE.open("r", errors="replace") as handle:
|
|
164
|
+
with CRASH_ARCHIVE.open("r", errors="replace", encoding="utf-8") as handle:
|
|
165
165
|
handle.seek(self.offset)
|
|
166
166
|
text = handle.read()
|
|
167
167
|
except OSError:
|
|
@@ -172,7 +172,7 @@ class CrashWatch:
|
|
|
172
172
|
|
|
173
173
|
def log_tail(lines: int = 80) -> str:
|
|
174
174
|
try:
|
|
175
|
-
content = RESOLVE_LOG.read_text(errors="replace").splitlines()
|
|
175
|
+
content = RESOLVE_LOG.read_text(errors="replace", encoding="utf-8").splitlines()
|
|
176
176
|
except OSError:
|
|
177
177
|
return ""
|
|
178
178
|
return "\n".join(content[-lines:])
|
|
@@ -56,7 +56,7 @@ def process_name(pid):
|
|
|
56
56
|
except Exception:
|
|
57
57
|
return ""
|
|
58
58
|
try: # Linux
|
|
59
|
-
with open("/proc/%d/comm" % pid) as handle:
|
|
59
|
+
with open("/proc/%d/comm" % pid, encoding="utf-8") as handle:
|
|
60
60
|
return handle.read().strip()
|
|
61
61
|
except (OSError, IOError):
|
|
62
62
|
pass
|
|
@@ -142,7 +142,7 @@ def main():
|
|
|
142
142
|
directory = os.path.dirname(REPORT_PATH)
|
|
143
143
|
if not os.path.isdir(directory):
|
|
144
144
|
os.makedirs(directory)
|
|
145
|
-
with open(REPORT_PATH, "w") as handle:
|
|
145
|
+
with open(REPORT_PATH, "w", encoding="utf-8") as handle:
|
|
146
146
|
json.dump(probe, handle, indent=2, sort_keys=True)
|
|
147
147
|
written = REPORT_PATH
|
|
148
148
|
except (OSError, IOError) as exc:
|
|
@@ -231,7 +231,7 @@ def main():
|
|
|
231
231
|
try:
|
|
232
232
|
if not os.path.isdir(REPORT_DIR):
|
|
233
233
|
os.makedirs(REPORT_DIR)
|
|
234
|
-
with open(path, "w") as handle:
|
|
234
|
+
with open(path, "w", encoding="utf-8") as handle:
|
|
235
235
|
json.dump(report, handle, indent=2, sort_keys=True)
|
|
236
236
|
written = path
|
|
237
237
|
except (OSError, IOError) as exc:
|
|
@@ -391,8 +391,8 @@ def _verdict(entry: Dict[str, Any], strategy: str) -> str:
|
|
|
391
391
|
|
|
392
392
|
|
|
393
393
|
def compare(gui_path: Path, headless_path: Path, out: Optional[Path]) -> int:
|
|
394
|
-
gui = json.loads(gui_path.read_text())
|
|
395
|
-
headless = json.loads(headless_path.read_text())
|
|
394
|
+
gui = json.loads(gui_path.read_text(encoding="utf-8"))
|
|
395
|
+
headless = json.loads(headless_path.read_text(encoding="utf-8"))
|
|
396
396
|
assert gui["metadata"]["headless"] is False, f"{gui_path} is not a GUI run"
|
|
397
397
|
assert headless["metadata"]["headless"] is True, f"{headless_path} is not a headless run"
|
|
398
398
|
|
|
@@ -385,7 +385,7 @@ def run(mode: str, out: Path) -> int:
|
|
|
385
385
|
|
|
386
386
|
|
|
387
387
|
def compare(gui_path: Path, headless_path: Path, out: Optional[Path]) -> int:
|
|
388
|
-
gui, headless = json.loads(gui_path.read_text()), json.loads(headless_path.read_text())
|
|
388
|
+
gui, headless = json.loads(gui_path.read_text(encoding="utf-8")), json.loads(headless_path.read_text(encoding="utf-8"))
|
|
389
389
|
assert gui["metadata"]["headless"] is False and headless["metadata"]["headless"] is True
|
|
390
390
|
lines = ["# Complex-cut round trip: what each format keeps", "",
|
|
391
391
|
"Two video tracks, audio, a per-item transform, clip colours, flags, item and "
|
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.
|
|
90
|
+
VERSION = "2.136.1"
|
|
91
91
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
92
92
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
93
93
|
logger.info(f"Detected platform: {get_platform()}")
|