versioncam 0.1.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.
Files changed (182) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE.md +105 -0
  3. package/README.md +463 -0
  4. package/bin/versioncam.js +29 -0
  5. package/dist/.types-render/render-page/draw.d.ts +28 -0
  6. package/dist/.types-render/render-page/main.d.ts +24 -0
  7. package/dist/.types-render/render-page/theme.d.ts +37 -0
  8. package/dist/app-server.d.ts +59 -0
  9. package/dist/app-server.js +328 -0
  10. package/dist/app-server.js.map +1 -0
  11. package/dist/cli/app.d.ts +13 -0
  12. package/dist/cli/app.js +21 -0
  13. package/dist/cli/app.js.map +1 -0
  14. package/dist/cli/commands/check.d.ts +8 -0
  15. package/dist/cli/commands/check.js +51 -0
  16. package/dist/cli/commands/check.js.map +1 -0
  17. package/dist/cli/commands/doctor.d.ts +8 -0
  18. package/dist/cli/commands/doctor.js +130 -0
  19. package/dist/cli/commands/doctor.js.map +1 -0
  20. package/dist/cli/commands/dsl.d.ts +16 -0
  21. package/dist/cli/commands/dsl.js +22 -0
  22. package/dist/cli/commands/dsl.js.map +1 -0
  23. package/dist/cli/commands/frame.d.ts +8 -0
  24. package/dist/cli/commands/frame.js +72 -0
  25. package/dist/cli/commands/frame.js.map +1 -0
  26. package/dist/cli/commands/init.d.ts +23 -0
  27. package/dist/cli/commands/init.js +109 -0
  28. package/dist/cli/commands/init.js.map +1 -0
  29. package/dist/cli/commands/inspect.d.ts +1 -0
  30. package/dist/cli/commands/inspect.js +32 -0
  31. package/dist/cli/commands/inspect.js.map +1 -0
  32. package/dist/cli/commands/install.d.ts +33 -0
  33. package/dist/cli/commands/install.js +66 -0
  34. package/dist/cli/commands/install.js.map +1 -0
  35. package/dist/cli/commands/login.d.ts +10 -0
  36. package/dist/cli/commands/login.js +49 -0
  37. package/dist/cli/commands/login.js.map +1 -0
  38. package/dist/cli/commands/measure.d.ts +1 -0
  39. package/dist/cli/commands/measure.js +36 -0
  40. package/dist/cli/commands/measure.js.map +1 -0
  41. package/dist/cli/commands/open-app.d.ts +14 -0
  42. package/dist/cli/commands/open-app.js +45 -0
  43. package/dist/cli/commands/open-app.js.map +1 -0
  44. package/dist/cli/commands/preview.d.ts +8 -0
  45. package/dist/cli/commands/preview.js +55 -0
  46. package/dist/cli/commands/preview.js.map +1 -0
  47. package/dist/cli/commands/record.d.ts +10 -0
  48. package/dist/cli/commands/record.js +86 -0
  49. package/dist/cli/commands/record.js.map +1 -0
  50. package/dist/cli/commands/render.d.ts +1 -0
  51. package/dist/cli/commands/render.js +93 -0
  52. package/dist/cli/commands/render.js.map +1 -0
  53. package/dist/cli/commands/review.d.ts +6 -0
  54. package/dist/cli/commands/review.js +89 -0
  55. package/dist/cli/commands/review.js.map +1 -0
  56. package/dist/cli/commands/sheet.d.ts +1 -0
  57. package/dist/cli/commands/sheet.js +48 -0
  58. package/dist/cli/commands/sheet.js.map +1 -0
  59. package/dist/cli/commands/stability.d.ts +14 -0
  60. package/dist/cli/commands/stability.js +110 -0
  61. package/dist/cli/commands/stability.js.map +1 -0
  62. package/dist/cli/main.d.ts +2 -0
  63. package/dist/cli/main.js +69 -0
  64. package/dist/cli/main.js.map +1 -0
  65. package/dist/cli/usage.d.ts +10 -0
  66. package/dist/cli/usage.js +46 -0
  67. package/dist/cli/usage.js.map +1 -0
  68. package/dist/config.d.ts +250 -0
  69. package/dist/config.js +154 -0
  70. package/dist/config.js.map +1 -0
  71. package/dist/core/camera.d.ts +30 -0
  72. package/dist/core/camera.js +94 -0
  73. package/dist/core/camera.js.map +1 -0
  74. package/dist/core/compose.d.ts +38 -0
  75. package/dist/core/compose.js +81 -0
  76. package/dist/core/compose.js.map +1 -0
  77. package/dist/core/cursor.d.ts +38 -0
  78. package/dist/core/cursor.js +103 -0
  79. package/dist/core/cursor.js.map +1 -0
  80. package/dist/core/easing.d.ts +15 -0
  81. package/dist/core/easing.js +33 -0
  82. package/dist/core/easing.js.map +1 -0
  83. package/dist/core/loop.d.ts +30 -0
  84. package/dist/core/loop.js +96 -0
  85. package/dist/core/loop.js.map +1 -0
  86. package/dist/core/motion-defaults.d.ts +61 -0
  87. package/dist/core/motion-defaults.js +62 -0
  88. package/dist/core/motion-defaults.js.map +1 -0
  89. package/dist/core/rng.d.ts +13 -0
  90. package/dist/core/rng.js +27 -0
  91. package/dist/core/rng.js.map +1 -0
  92. package/dist/core/sse.d.ts +15 -0
  93. package/dist/core/sse.js +16 -0
  94. package/dist/core/sse.js.map +1 -0
  95. package/dist/core/timeline.d.ts +146 -0
  96. package/dist/core/timeline.js +81 -0
  97. package/dist/core/timeline.js.map +1 -0
  98. package/dist/core/timing.d.ts +31 -0
  99. package/dist/core/timing.js +29 -0
  100. package/dist/core/timing.js.map +1 -0
  101. package/dist/core/typing.d.ts +12 -0
  102. package/dist/core/typing.js +35 -0
  103. package/dist/core/typing.js.map +1 -0
  104. package/dist/driver/clip.d.ts +71 -0
  105. package/dist/driver/clip.js +120 -0
  106. package/dist/driver/clip.js.map +1 -0
  107. package/dist/driver/compare.d.ts +34 -0
  108. package/dist/driver/compare.js +40 -0
  109. package/dist/driver/compare.js.map +1 -0
  110. package/dist/driver/gate.d.ts +36 -0
  111. package/dist/driver/gate.js +27 -0
  112. package/dist/driver/gate.js.map +1 -0
  113. package/dist/driver/launch.d.ts +43 -0
  114. package/dist/driver/launch.js +47 -0
  115. package/dist/driver/launch.js.map +1 -0
  116. package/dist/driver/page-hooks.d.ts +72 -0
  117. package/dist/driver/page-hooks.js +129 -0
  118. package/dist/driver/page-hooks.js.map +1 -0
  119. package/dist/driver/reports.d.ts +34 -0
  120. package/dist/driver/reports.js +42 -0
  121. package/dist/driver/reports.js.map +1 -0
  122. package/dist/driver/session.d.ts +285 -0
  123. package/dist/driver/session.js +773 -0
  124. package/dist/driver/session.js.map +1 -0
  125. package/dist/driver/settle.d.ts +41 -0
  126. package/dist/driver/settle.js +82 -0
  127. package/dist/driver/settle.js.map +1 -0
  128. package/dist/env.d.ts +11 -0
  129. package/dist/env.js +41 -0
  130. package/dist/env.js.map +1 -0
  131. package/dist/fixtures.d.ts +13 -0
  132. package/dist/fixtures.js +13 -0
  133. package/dist/fixtures.js.map +1 -0
  134. package/dist/index.d.ts +30 -0
  135. package/dist/index.js +16 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/inspect/inspect.d.ts +70 -0
  138. package/dist/inspect/inspect.js +176 -0
  139. package/dist/inspect/inspect.js.map +1 -0
  140. package/dist/inspect/measure.d.ts +40 -0
  141. package/dist/inspect/measure.js +107 -0
  142. package/dist/inspect/measure.js.map +1 -0
  143. package/dist/loader.d.ts +28 -0
  144. package/dist/loader.js +143 -0
  145. package/dist/loader.js.map +1 -0
  146. package/dist/page/assets/index-DUom5amc.js +1 -0
  147. package/dist/page/index.html +18 -0
  148. package/dist/render/encode.d.ts +40 -0
  149. package/dist/render/encode.js +183 -0
  150. package/dist/render/encode.js.map +1 -0
  151. package/dist/render/ffmpeg.d.ts +13 -0
  152. package/dist/render/ffmpeg.js +72 -0
  153. package/dist/render/ffmpeg.js.map +1 -0
  154. package/dist/render/presentation.d.ts +19 -0
  155. package/dist/render/presentation.js +27 -0
  156. package/dist/render/presentation.js.map +1 -0
  157. package/dist/render/render.d.ts +59 -0
  158. package/dist/render/render.js +144 -0
  159. package/dist/render/render.js.map +1 -0
  160. package/dist/render/sampling.d.ts +47 -0
  161. package/dist/render/sampling.js +129 -0
  162. package/dist/render/sampling.js.map +1 -0
  163. package/dist/render/sequence.d.ts +24 -0
  164. package/dist/render/sequence.js +105 -0
  165. package/dist/render/sequence.js.map +1 -0
  166. package/dist/render/serve.d.ts +35 -0
  167. package/dist/render/serve.js +124 -0
  168. package/dist/render/serve.js.map +1 -0
  169. package/dist/review/review.d.ts +54 -0
  170. package/dist/review/review.js +229 -0
  171. package/dist/review/review.js.map +1 -0
  172. package/dist/scene.d.ts +23 -0
  173. package/dist/scene.js +2 -0
  174. package/dist/scene.js.map +1 -0
  175. package/dsl.md +119 -0
  176. package/package.json +76 -0
  177. package/plugin/.claude-plugin/plugin.json +9 -0
  178. package/plugin/README.md +105 -0
  179. package/plugin/agents/versioncam-reviewer.md +63 -0
  180. package/plugin/skills/versioncam/SKILL.md +235 -0
  181. package/plugin/skills/versioncam/authoring.md +226 -0
  182. package/plugin/skills/versioncam/onboarding.md +199 -0
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "versioncam",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "Record demo clips of a real web app: scripted, deterministic, regenerated on deploy.",
6
+ "license": "FSL-1.1-ALv2",
7
+ "author": "Peter Bulovec",
8
+ "homepage": "https://version.cam",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/petbul/versioncam.git",
12
+ "directory": "packages/recorder"
13
+ },
14
+ "bugs": "https://github.com/petbul/versioncam-home/issues",
15
+ "keywords": [
16
+ "demo",
17
+ "video",
18
+ "screen-recording",
19
+ "playwright",
20
+ "docs",
21
+ "ci",
22
+ "deterministic",
23
+ "agent-skill"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bin": {
29
+ "versioncam": "bin/versioncam.js"
30
+ },
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ },
36
+ "./scene": {
37
+ "types": "./dist/scene.d.ts",
38
+ "import": "./dist/scene.js"
39
+ },
40
+ "./fixtures": {
41
+ "types": "./dist/fixtures.d.ts",
42
+ "import": "./dist/fixtures.js"
43
+ }
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "!dist/**/*.test.*",
48
+ "bin",
49
+ "dsl.md",
50
+ "plugin",
51
+ "CHANGELOG.md"
52
+ ],
53
+ "engines": {
54
+ "node": ">=22"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -b --force tsconfig.json && vite build -c vite.render.config.ts",
58
+ "test:integration": "env -u NODE_OPTIONS playwright test -c test/playwright.config.ts",
59
+ "typecheck": "tsc -p tsconfig.render.json"
60
+ },
61
+ "dependencies": {
62
+ "esbuild": "^0.25.0",
63
+ "playwright": "1.59.1"
64
+ },
65
+ "optionalDependencies": {
66
+ "ffmpeg-static": "^5.2.0"
67
+ },
68
+ "devDependencies": {
69
+ "@playwright/test": "1.59.1",
70
+ "@types/node": "^22.10.0",
71
+ "@types/pngjs": "^6.0.5",
72
+ "jsdom": "^26.0.0",
73
+ "pngjs": "^7.0.0",
74
+ "vite": "^8.0.16"
75
+ }
76
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "versioncam",
3
+ "description": "Record a short, true demo clip of a web app from one sentence: a skill that sets the repository up on its first run, writes the clip, and has a reviewer pass it from a contact sheet.",
4
+ "author": {
5
+ "name": "Peter Bulovec"
6
+ },
7
+ "homepage": "https://version.cam",
8
+ "keywords": ["demo", "recording", "playwright", "screencast"]
9
+ }
@@ -0,0 +1,105 @@
1
+ # The versioncam skill
2
+
3
+ `/versioncam "<what the clip should show>"` records a clip of your web app: it
4
+ writes the script, records it, has a reviewer look at the result, and fixes
5
+ what the reviewer finds — until the reviewer passes it or the rounds run out.
6
+ The first time it runs in a repository, it sets the repository up first.
7
+
8
+ Nothing here calls an API. The model is whichever agent session runs the
9
+ skill, and the vision step is that session's file reader opening a contact
10
+ sheet. Everything is markdown, and you can edit all of it.
11
+
12
+ ## Install
13
+
14
+ The skill ships inside the `versioncam` npm package:
15
+
16
+ ```bash
17
+ npx versioncam init # .claude/skills/versioncam/ and .claude/agents/versioncam-reviewer.md
18
+ ```
19
+
20
+ `init` installs the skill and nothing else — no config: the first run writes
21
+ that. It refuses to overwrite a file that is already there, and refuses the
22
+ whole install if any one is; `--force` overwrites. For an agent host that reads
23
+ skills from somewhere other than `.claude/`, `--into <dir>` puts the skill
24
+ directory at `<dir>/versioncam/`, with the reviewer inside it.
25
+
26
+ Or, without copying anything, load the plugin directory as it is:
27
+
28
+ ```bash
29
+ claude --plugin-dir node_modules/versioncam/plugin
30
+ ```
31
+
32
+ ## Run it
33
+
34
+ ```
35
+ /versioncam "Create a shipment for a new recipient and show it appearing in the list"
36
+ /versioncam "Find a shipment by filtering the list" --id 02-find --max-rounds 4
37
+ ```
38
+
39
+ ## What it does
40
+
41
+ **The first run** — no `versioncam.config.ts` in the directory — sets the
42
+ repository up (`skills/versioncam/onboarding.md`): it works out how the app
43
+ starts and where it is served, from `package.json`, the framework's config and
44
+ the README, and asks when there is more than one plausible answer; asks whether
45
+ the app signs in and how you want that handled; writes the config, with a
46
+ `webServer` so every command starts the app itself; runs `doctor` and
47
+ `inspect` to prove the app starts and settles; and records a three-second clip
48
+ twice with `stability`, to prove the app records the same way twice before a
49
+ clip is written for it. Nothing is committed.
50
+
51
+ **Every run** then (`skills/versioncam/SKILL.md`):
52
+
53
+ 1. `versioncam doctor` — which starts the app — and `inspect /`.
54
+ 2. The session writes the clip itself, following
55
+ `skills/versioncam/authoring.md`: it reads `versioncam dsl` and the existing
56
+ clips, writes a beat sheet, writes the clip, records it in draft, and runs
57
+ the deterministic checks in `versioncam review`. Three recordings at most,
58
+ then on. On a page too big to hold in context, it hands this to a subagent
59
+ and keeps that subagent for later rounds.
60
+ 3. A contact sheet sampled at the beats, and a **fresh reviewer** on it — one
61
+ picture, a five-item checklist, a JSON verdict. Fresh every round,
62
+ deliberately: it sees the clip, never the argument for it.
63
+ 4. On a pass, a full-quality record and render. Otherwise the fixes the verdict
64
+ names, and another round.
65
+
66
+ The reviewer is `agents/versioncam-reviewer.md`, one file used three ways:
67
+ registered as an agent, which is what Claude Code does with it; as the prompt
68
+ of a plain subagent, on a host that cannot register agents; or as a checklist
69
+ the session applies to its own sheet, on a host with no subagents at all. The
70
+ verdict records which, as `"reviewer": "restricted"`, `"unrestricted"` or
71
+ `"self"` — a clip passed by its own author is a weaker claim, and says so.
72
+
73
+ The skill never runs `git`. The clip it writes lands in your clips directory as
74
+ an ordinary file, for you to read and commit yourself.
75
+
76
+ ## What it leaves behind
77
+
78
+ ```
79
+ .versioncam/author/<id>/
80
+ beats.json the beat sheet: { label, expect } per beat
81
+ summary.json { id, rounds, passed, wallMs, reviewer }
82
+ <id>-sheet.png the final contact sheet
83
+ round-1/
84
+ author.md what the round produced, including what was awkward
85
+ beats.json
86
+ verdict.json the reviewer's JSON, verbatim
87
+ round-2/ …
88
+ ```
89
+
90
+ `.versioncam/` is a build product and is gitignored. Keep the clip; the
91
+ transcripts are for reading when something goes wrong, and for finding the
92
+ next thing to fix in the recorder — the paragraph on awkward tooling is there
93
+ because the first agent to write one produced a list that became a work
94
+ package.
95
+
96
+ ## What it costs
97
+
98
+ On the example app, headless, with an earlier two-agent version of this loop:
99
+ one clip took between six and twelve minutes, in one or two rounds, and all six
100
+ intents across two apps passed. Most of the time is model latency rather than
101
+ the recorder: a round is a handful of recordings of a few seconds each, and a
102
+ reviewer's single look at a single picture still takes a minute or two end to
103
+ end. It runs on a subscription rather than a metered key, so the setup cost is
104
+ close to zero; for anything higher-volume the prompts in these files are what
105
+ would move to a direct SDK loop, unchanged.
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: versioncam-reviewer
3
+ description: Judges one recorded demo clip from its contact sheet against a fixed five-item checklist and returns a JSON verdict. Spawned fresh for each round by the versioncam skill, with only the sheet path, the beat sheet and the clip id — it never sees how the clip was written.
4
+ tools: Read, Bash(npx versioncam frame *)
5
+ model: sonnet
6
+ effort: low
7
+ maxTurns: 8
8
+ omitClaudeMd: true
9
+ ---
10
+
11
+ You are given one contact sheet: a single PNG of tiles from one clip, in time
12
+ order, left to right then top to bottom. You are also given the clip's beat
13
+ sheet and its id. Read the sheet and say whether the clip is watchable.
14
+
15
+ ## The checklist
16
+
17
+ 1. The cursor starts clear of the controls in the first tile — not hovering a
18
+ button, a row or a field.
19
+ 2. Each beat's expected effect is visible in some tile.
20
+ 3. Nothing a beat depends on is cropped by the camera framing.
21
+ 4. A caption is present and legible.
22
+ 5. The final tile has nothing left open — no dialog, menu or tooltip.
23
+
24
+ ## Rules
25
+
26
+ - **Judge only what you can see.** The beat sheet says what was intended; it is
27
+ not evidence that it happened. If a tile does not show it, it did not
28
+ happen as far as this review is concerned.
29
+ - If a tile is too small to be sure, run `npx versioncam frame <id> <n>` for that
30
+ frame at full size and read the PNG it prints. That is the only shell
31
+ command you run.
32
+ - **Tile *n* is not frame *n*.** You are given the frame number behind each
33
+ tile; pull those. A reviewer that guessed pulled frames 1 to 16 of a clip
34
+ whose beat fired at frame 29, watched nothing happen, and failed a clip that
35
+ was fine. If you were not given the numbers, say so in `legibility` and judge
36
+ from the tiles alone rather than inventing frames to pull.
37
+ - Say which tile each observation comes from.
38
+ - If you genuinely cannot tell after looking at the full-size frame, fail the
39
+ item and say what you could not make out. Guessing in either direction is
40
+ worse than saying so.
41
+
42
+ ## Return
43
+
44
+ Only JSON. No preamble, no commentary around it.
45
+
46
+ ```json
47
+ {
48
+ "items": [
49
+ { "item": "cursor starts clear of controls", "pass": true, "note": "tile 1: cursor sits over empty page margin" },
50
+ { "item": "beat: shipment created", "pass": false, "note": "no tile shows a new row; tiles 4-6 show the spinner" }
51
+ ],
52
+ "pass": false,
53
+ "legibility": "1440x900 downscaled to 8 tiles; body text unreadable, headings and buttons clear",
54
+ "reviewer": "restricted"
55
+ }
56
+ ```
57
+
58
+ One entry in `items` for each of the five checklist points, plus one for each
59
+ beat in the beat sheet, labelled `beat: <label>`. `pass` at the top level is
60
+ true only when every item passed. `legibility` is one sentence on how much of
61
+ the sheet you could actually read, so the next round knows how much to trust
62
+ this verdict. `reviewer` is always `"restricted"`: it records that the verdict
63
+ came from this file, run as an agent that saw only the sheet.
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: versioncam
3
+ description: Record a short demo clip — a screencast, a short video, a GIF of a feature — of this repository's web app from one sentence of what it should show, and iterate until a reviewer passes it from a contact sheet. Use when someone asks to record, film or make a demo of a feature, or to set versioncam up in a repository. Works in any repository with a web app — the first run writes versioncam.config.ts, works out how to start the app, and proves the app records the same way twice before anything is authored.
4
+ user-invocable: true
5
+ argument-hint: "\"<what the clip should show>\" [--id <slug>] [--max-rounds 6]"
6
+ allowed-tools:
7
+ - Agent
8
+ - SendMessage
9
+ - AskUserQuestion
10
+ - Read
11
+ - Write
12
+ - Edit
13
+ - Glob
14
+ - Grep
15
+ - Bash(npx versioncam *)
16
+ - Bash(mkdir *)
17
+ - Bash(cp *)
18
+ - Bash(ls *)
19
+ ---
20
+
21
+ # Record a demo clip
22
+
23
+ `$ARGUMENTS`
24
+
25
+ You turn one sentence into a clip: a script in this repository that drives the
26
+ running app through its own controls, and a video recorded from it. You write
27
+ the clip; a reviewer who has never seen how it was written looks at a picture
28
+ of it and says whether it shows what it claims; and you go round again until it
29
+ does, or the rounds run out.
30
+
31
+ Follow these steps in order. Two more files sit beside this one, in this
32
+ skill's directory: `onboarding.md` and `authoring.md`. They are briefs, read
33
+ when a step says to.
34
+
35
+ ## 0. Which phase
36
+
37
+ First, check versioncam is installed where Node will find it:
38
+ `ls -d node_modules/versioncam`, then the same in each parent directory up to
39
+ the repository root (`../node_modules/versioncam`, …) — a workspace hoists it.
40
+ If none has it, **stop** and say it must be installed first
41
+ (`npm i -D versioncam`, then `npx versioncam install`). Every step below runs
42
+ `npx versioncam`, and `npx` with nothing installed downloads the latest
43
+ published version — not necessarily the one this repository will pin, and
44
+ with no Chromium to record with.
45
+
46
+ Then: is there a `versioncam.config.ts` (or `.js`, `.mjs`) in the working
47
+ directory?
48
+
49
+ - **No** → this is the repository's first run. Read `onboarding.md` and do all
50
+ of it, then continue with step 1.
51
+ - **Yes** → continue with step 1.
52
+
53
+ ## 1. Set up
54
+
55
+ Parse `$ARGUMENTS`. The first quoted string is the **intent**. `--id <slug>`
56
+ names the clip; without it, derive a slug from the intent — lowercase, hyphens,
57
+ at most 40 characters, cut at a word boundary. `--max-rounds <n>` defaults to 6.
58
+
59
+ ```bash
60
+ mkdir -p .versioncam/author/<id>
61
+ npx versioncam doctor
62
+ ```
63
+
64
+ `doctor` checks the machine and the config, and **starts the app** through the
65
+ config's `webServer` when nothing is serving it, then stops it again. Every
66
+ later command does the same, so you never start a dev server yourself. If the
67
+ app check fails, **stop** and say so: nothing below works without the app, and
68
+ every round would spend a recording discovering that. If another check fails,
69
+ print it and stop too.
70
+
71
+ Then capture the start page once:
72
+
73
+ ```bash
74
+ npx versioncam inspect /
75
+ ```
76
+
77
+ ## 2. Author — this session
78
+
79
+ Read `authoring.md` now: it is how to write and repair a clip, and it applies
80
+ to you. Then, as it says: `npx versioncam dsl`, the existing clips, `inspect`
81
+ of every page the intent touches; write `.versioncam/author/<id>/beats.json`;
82
+ write the clip; `record --draft`; `review --beats …`; repair — **at most three
83
+ recordings**, then on to the sheet, passed or not.
84
+
85
+ Write what the round produced to `.versioncam/author/<id>/round-N/author.md` —
86
+ the four things `authoring.md` asks for, including the paragraph on what was
87
+ awkward about the tools. That paragraph is the point of keeping these
88
+ directories; do not summarise it away. Copy `beats.json` beside it.
89
+
90
+ **Delegation.** If the first `inspect /` printed more than 400 lines, or your
91
+ host makes subagents cheap, hand the authoring to a subagent instead of doing
92
+ it here. Spawn a plain one (general-purpose) whose prompt is the full text of
93
+ `authoring.md`, followed by the intent, the clip id, the clips directory (the
94
+ config's `clips`), the `inspect /` output inline, and the beats path. **Keep the
95
+ agent id it returns**: that id is how you continue the same author in later
96
+ rounds, with `SendMessage`, everything it learned about the page still in its
97
+ context — spawning a new one would start from nothing and repeat the same
98
+ recordings. Save its reply as `round-N/author.md`. You then orchestrate; the
99
+ rest of this file is the same either way.
100
+
101
+ ## 3. The sheet, then a fresh reviewer
102
+
103
+ ```bash
104
+ npx versioncam sheet <id> --at-marks
105
+ ```
106
+
107
+ `--at-marks` samples frame 0, each beat's settle mark, and the last frame,
108
+ instead of eight evenly spaced points — so the tiles the reviewer sees are the
109
+ beats, not whatever happened to fall on an eighth of the duration. It prints
110
+ the sheet's path, then the frame number behind each tile.
111
+
112
+ The reviewer is given only:
113
+
114
+ - the sheet path,
115
+ - **the frame numbers `sheet --at-marks` just printed**, in order, one per tile,
116
+ - the contents of `.versioncam/author/<id>/beats.json`,
117
+ - the clip id (so it can call `npx versioncam frame <id> <n>` — from this
118
+ directory, which you name if a subagent on your host starts somewhere else).
119
+
120
+ The frame numbers are not optional. A reviewer given only the id assumed tile
121
+ *n* was frame *n*, pulled frames 1 to 16 — every one of them before the beat
122
+ fired at frame 29 — and reported that the clip never filtered anything. It was
123
+ looking at the right clip and the wrong seconds. `sheet` prints the list for
124
+ exactly this reason; pass it on verbatim.
125
+
126
+ The reviewer's brief is `versioncam-reviewer.md`: in `agents/` two levels up
127
+ from this skill's directory when the skill came from the plugin, in
128
+ `.claude/agents/` when `versioncam init` installed it, and beside this file
129
+ when it was installed with `--into`. Take the first rung of this ladder your
130
+ host allows:
131
+
132
+ 1. **Registered.** If an agent named `versioncam-reviewer` is available — it is
133
+ `versioncam:versioncam-reviewer` when it came with the plugin — spawn it
134
+ with those inputs. It is blind by construction: a fresh context holding the
135
+ sheet and the beats and nothing of how the clip was made. Its JSON says
136
+ `"reviewer": "restricted"`.
137
+ 2. **A plain subagent.** Otherwise, spawn a general-purpose subagent whose
138
+ prompt is the full text of `versioncam-reviewer.md` followed by those
139
+ inputs. It is blind by instruction rather than by construction. Its JSON
140
+ will still say `"restricted"`, because the file it was given says so; save
141
+ it with `"reviewer": "unrestricted"`, which is what it was.
142
+ 3. **No subagents.** Otherwise, read `versioncam-reviewer.md` and judge the
143
+ sheet yourself, in a step of its own — *before* you look at your beats or
144
+ your clip again, with only the four inputs in front of you — and save the
145
+ verdict with `"reviewer": "self"`. The summary says so: a clip passed by its
146
+ own author is a weaker claim, and whoever reads it should know.
147
+
148
+ A fresh reviewer every round is the point, not an accident. It must not see
149
+ the author's reasoning, an earlier verdict, or how hard the clip was to get
150
+ working — only this round's picture. Never continue a reviewer, and never pass
151
+ it the author's report.
152
+
153
+ Save its JSON verbatim, with `reviewer` as above, as
154
+ `.versioncam/author/<id>/round-N/verdict.json`.
155
+
156
+ ### If you think the verdict is wrong
157
+
158
+ You will sometimes believe a reviewer has erred, and sometimes you will be
159
+ right. **Do not override it.** Overriding is the one move that undoes the
160
+ design: a fresh reviewer is trusted precisely because it has not watched the
161
+ clip being built, and a session that has watched three rounds is the least
162
+ independent judge available.
163
+
164
+ Get **one** second opinion, from a fresh reviewer on the same rung — same
165
+ sheet, same beats, same frame numbers, and nothing about the first verdict or
166
+ what you think it got wrong. Save it as `round-N/verdict-2.json`. That verdict
167
+ stands, whichever way it goes. If it agrees with the first, the clip has failed
168
+ and the round continues; if it disagrees, take it and move on, and keep both
169
+ files so the disagreement is on the record.
170
+
171
+ One second opinion, once per round. Two reviewers who disagree is information.
172
+ A third is shopping for the answer you wanted.
173
+
174
+ ## 4. If the verdict passes
175
+
176
+ Take the real recording and render it:
177
+
178
+ ```bash
179
+ npx versioncam record <id>
180
+ npx versioncam render <id>
181
+ npx versioncam sheet <id> --at-marks
182
+ ```
183
+
184
+ Copy that final sheet into `.versioncam/author/<id>/`. Write
185
+ `.versioncam/author/<id>/summary.json`:
186
+
187
+ ```json
188
+ { "id": "<id>", "rounds": 2, "passed": true, "wallMs": 812000, "reviewer": "restricted" }
189
+ ```
190
+
191
+ `reviewer` is the rung the passing verdict came from. `wallMs` only if you can
192
+ measure it — leave it out rather than guess; a harness running you measures it
193
+ from outside. After a first run that found the app unstable, add
194
+ `"stable": false`.
195
+
196
+ Name the clip file and the rendered video, and stop.
197
+
198
+ ## 5. If this was the last round
199
+
200
+ If the verdict failed and `N == max-rounds`, write `summary.json` with
201
+ `"passed": false` and the last verdict inline. Say which items failed, and
202
+ stop; in a headless run, say plainly in your final message that it failed.
203
+
204
+ Do not quietly raise `max-rounds`. A clip that has not converged in six rounds
205
+ is a finding about the intent or the tools, and it is worth more written down
206
+ than ground out.
207
+
208
+ ## 6. Otherwise — fix it, and go round again
209
+
210
+ Fix what the verdict names — the items that failed, read as written — and
211
+ nothing it did not name. When the authoring is delegated, send the author the
212
+ verdict JSON **verbatim** with `SendMessage`, by the id you kept — not
213
+ summarised, not reordered, not softened — and the instruction: fix the failing
214
+ items, re-record in draft, run `npx versioncam review`, and return. Add no
215
+ opinion of your own: the reviewer has looked at the clip in a way you have
216
+ not, and the author trusts the verdict because it is a reviewer's and not a
217
+ relayed impression.
218
+
219
+ Re-record in draft, run `review`, make `.versioncam/author/<id>/round-N/` for
220
+ the new round with its `author.md` and `beats.json`, and go back to step 3.
221
+
222
+ ## What this leaves behind
223
+
224
+ ```
225
+ .versioncam/author/<id>/
226
+ beats.json
227
+ summary.json { id, rounds, passed, wallMs?, reviewer }
228
+ <id>-sheet.png the final sheet
229
+ round-1/ author.md beats.json verdict.json (verdict-2.json)
230
+ round-2/ …
231
+ ```
232
+
233
+ `.versioncam/` is gitignored — a build product, none of it to be committed. The
234
+ clip is not: it lives in the clips directory and is the thing worth reading and
235
+ keeping. You commit nothing; that is the person's call.