ace-demo 0.23.3 → 0.25.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -0
- data/README.md +30 -2
- data/handbook/skills/as-demo-analyze-cast/SKILL.md +27 -0
- data/handbook/workflow-instructions/demo/analyze-cast.wf.md +53 -0
- data/handbook/workflow-instructions/demo/create.wf.md +32 -6
- data/handbook/workflow-instructions/demo/record.wf.md +64 -2
- data/lib/ace/demo/atoms/asciinema_tape_compiler.rb +2 -0
- data/lib/ace/demo/atoms/demo_yaml_parser.rb +127 -8
- data/lib/ace/demo/atoms/record_option_validator.rb +14 -0
- data/lib/ace/demo/atoms/vhs_tape_compiler.rb +2 -0
- data/lib/ace/demo/atoms/yaml_record_planner.rb +1 -0
- data/lib/ace/demo/cli/commands/record.rb +36 -2
- data/lib/ace/demo/cli/commands/verify.rb +72 -0
- data/lib/ace/demo/cli.rb +4 -0
- data/lib/ace/demo/models/recording_result.rb +4 -2
- data/lib/ace/demo/models/verification_result.rb +12 -2
- data/lib/ace/demo/molecules/asciinema_executor.rb +11 -3
- data/lib/ace/demo/molecules/cast_verifier.rb +160 -6
- data/lib/ace/demo/molecules/recording_manifest_writer.rb +47 -0
- data/lib/ace/demo/molecules/tmux_directive_executor.rb +80 -0
- data/lib/ace/demo/molecules/verification_report_writer.rb +128 -0
- data/lib/ace/demo/molecules/vhs_executor.rb +34 -1
- data/lib/ace/demo/organisms/demo_recorder.rb +64 -11
- data/lib/ace/demo/version.rb +1 -1
- data/lib/ace/demo.rb +4 -0
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3dc9853e3f70693cc0dc687ab61f645ed432d0afae57a7dd2d7e87c746da9236
|
|
4
|
+
data.tar.gz: 4f85e3e89c5efefdbab60d3776e4a2c6b92feb2067a1bdbdc7954723caada2c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fff3491b92a232bbbfe39aaf01ddf6f0e3924ff0d9644609572248d4338b0ecceadbcbd5a19f0854398e43c482864d2336eea6a9aaceb97e4ba9a9dda6a654f2
|
|
7
|
+
data.tar.gz: dada165a45b5b184b95f5a49cf30775a8e4290530f13e4ea75337aacfcf2cb4cb4267da621a3311ac2620b82c6d8c71c22a680acc3cb89e0fdc78cb8f9ff342c
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,93 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
|
+
### Fixed
|
|
10
|
+
- Failed YAML tape verification by default when the final asciinema shell exit is non-zero, unless `verify.allow_nonzero_exit: true` opts out explicitly.
|
|
11
|
+
- Surfaced non-zero cast exit codes in `record` / `verify` output and verification reports so broken recordings are diagnosable without opening the raw cast.
|
|
12
|
+
|
|
13
|
+
## [0.25.6] - 2026-04-16
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Added additive YAML `tmux:` recorder-control directives for asciinema-backed demo tapes, covering shared-surface `attach`, `detach`, `wait`, `send`, and optional `capture` actions alongside existing visible `type:` commands.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Updated `ace-demo` recording/docs to treat tmux orchestration as structured recorder control instead of canonical raw tmux shell glue.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Updated the `ace-tmux` runtime dependency constraint to `~> 0.17` so recorder-side tmux directives stay aligned with the new runtime inspection release line.
|
|
23
|
+
- Rejected unsupported YAML `tmux.action: capture` directives during parsing, forwarded recording env values into recorder-side tmux control resolution, and clarified that tmux directives are supported only for the asciinema backend.
|
|
24
|
+
- Rejected YAML `tmux:` directives when the resolved recording backend is `vhs` so unsupported backend/control-surface combinations fail fast instead of being silently ignored during VHS compilation.
|
|
25
|
+
|
|
26
|
+
## [0.25.1] - 2026-04-16
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Exported resolved browser executable variables for VHS runs and aligned non-browser E2E verification with explicit constrained-environment failure evidence.
|
|
30
|
+
|
|
31
|
+
## [0.25.0] - 2026-04-14
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Rewrote `TS-DEMO-001` smoke E2E coverage around public-surface goal contracts by adding non-dry-run preset recording success coverage and stabilizing dry-run and missing-`--pr` verification assertions.
|
|
35
|
+
|
|
36
|
+
### Technical
|
|
37
|
+
- Added `TC-005-record-preset-success-artifact` runner/verifier assets and updated scenario/aggregate E2E manifests to execute and verify 5 goals.
|
|
38
|
+
- Updated scenario setup to use `${ACE_E2E_SOURCE_ROOT:-$PROJECT_ROOT_PATH}` for resilient `mise.toml` bootstrap in package sandbox runs.
|
|
39
|
+
|
|
40
|
+
## [0.24.6] - 2026-04-15
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- Made `ace-demo record` print verification summaries safely when optional failure detail arrays are absent, so reruns surface the real verification outcome instead of crashing in CLI output formatting.
|
|
44
|
+
|
|
45
|
+
## [0.24.5] - 2026-04-15
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- Added `ace-demo verify` plus a dedicated cast-analysis workflow so failed asciinema recordings can be re-verified and routed to scenario, product, or verifier fixes before re-recording.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Expanded YAML demo verification with required visible output and ordered output-transition checks, renamed scenario-side failures to `scenario_defect`, and preserved failed recording sandboxes/manifests for follow-up triage.
|
|
52
|
+
|
|
53
|
+
## [0.24.4] - 2026-04-15
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- Tightened demo planning and recording workflows so tmux/UI demos define the visible transition up front and validate the operator viewpoint before recording.
|
|
57
|
+
|
|
58
|
+
### Technical
|
|
59
|
+
- Refreshed the handbook demo workflow instructions to reject recordings that miss the visible `before -> trigger -> effect -> after` contract for state-transition demos.
|
|
60
|
+
|
|
61
|
+
## [0.24.3] - 2026-04-13
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
- Completed the batch i05 migration follow-through for this package and aligned it with the restarted `fast` / `feat` / `e2e` verification model.
|
|
65
|
+
|
|
66
|
+
### Technical
|
|
67
|
+
- Included in the coordinated assignment-driven patch release for batch i05 package updates.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## [0.24.2] - 2026-04-12
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
- Migrated deterministic `ace-demo` coverage to `test/fast`, kept scenario assets in `test/e2e`, and aligned E2E metadata references to the new fast test paths.
|
|
74
|
+
- Added package-level testing contract guidance for `ace-test ace-demo`, `ace-test ace-demo all`, and `ace-test-e2e ace-demo`.
|
|
75
|
+
|
|
76
|
+
## [0.24.1] - 2026-04-07
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
- Added fail-closed verification behavior for recording workflows with richer report classification when verification fails.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
- Updated demo CLI/recording paths and parser logic to preserve typed failure semantics through verification and recorder execution.
|
|
83
|
+
|
|
84
|
+
## [0.24.0] - 2026-04-07
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
- Added semantic `verify:` support for YAML/asciinema demo tapes, including required exported variables, forbidden output signatures, and final-state assertion commands.
|
|
88
|
+
- Added structured demo verification reports under `.ace-local/demo/` so failed recordings preserve actionable evidence for retry or bug triage.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
- Made `ace-demo record` fail closed on verification errors instead of treating cast mismatches as warning-only output.
|
|
92
|
+
- Classified recording failures as `instruction_defect`, `product_bug`, or `verification_error`, and blocked PR upload/comment when verification does not pass.
|
|
93
|
+
|
|
94
|
+
### Technical
|
|
95
|
+
- Expanded parser, verifier, recorder, and CLI coverage for semantic verification, report writing, and fail-closed record behavior.
|
|
9
96
|
|
|
10
97
|
## [0.23.3] - 2026-03-29
|
|
11
98
|
|
data/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|

|
|
20
20
|
|
|
21
|
-
`ace-demo` records terminal sessions as proof-of-work evidence for agent-driven workflows. Tapes define what to capture — either as simple [VHS](https://github.com/charmbracelet/vhs) scripts (`.tape`) or as YAML specs (`.tape.yml`) with sandbox setup, scenes, and
|
|
21
|
+
`ace-demo` records terminal sessions as proof-of-work evidence for agent-driven workflows. Tapes define what to capture — either as simple [VHS](https://github.com/charmbracelet/vhs) scripts (`.tape`) or as YAML specs (`.tape.yml`) with sandbox setup, scenes, teardown, and optional tmux recorder-control directives.
|
|
22
22
|
|
|
23
23
|
Recordings attach directly to GitHub pull requests as reviewable evidence. Requires `vhs`, `chromium`, and `ttyd` for deterministic rendering (see [setup requirements](docs/setup.md)).
|
|
24
24
|
|
|
@@ -43,6 +43,11 @@ scenes:
|
|
|
43
43
|
commands:
|
|
44
44
|
- type: ace-demo list
|
|
45
45
|
sleep: 4s
|
|
46
|
+
- tmux:
|
|
47
|
+
action: wait
|
|
48
|
+
for: window-active
|
|
49
|
+
session: fork-demo
|
|
50
|
+
window: work
|
|
46
51
|
- type: ace-demo record hello
|
|
47
52
|
sleep: 6s
|
|
48
53
|
|
|
@@ -51,10 +56,12 @@ teardown:
|
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
- **setup** — sandbox isolation, git init, fixture copying, or arbitrary shell via `run: <cmd>`
|
|
54
|
-
- **scenes** — named command sequences
|
|
59
|
+
- **scenes** — named command sequences with visible `type:` shell commands and optional `tmux:` recorder-control directives for asciinema-backed recordings
|
|
55
60
|
- **teardown** — cleanup directives that always run (even on failure)
|
|
56
61
|
- **settings** — optional `font_size`, `width`, `height`, `format` overrides
|
|
57
62
|
|
|
63
|
+
Use `type:` for on-camera shell commands that should appear in the recording. Use `tmux:` only with the asciinema backend for recorder-control actions such as `attach`, `detach`, `wait`, and `send` when the demo needs deterministic tmux choreography without raw shell glue. YAML tapes recorded with the `vhs` backend must not include `tmux:` directives.
|
|
64
|
+
|
|
58
65
|
Legacy `.tape` files use raw VHS syntax directly. See the [Usage Guide](docs/usage.md) for the full tape specification.
|
|
59
66
|
|
|
60
67
|
## Use Cases
|
|
@@ -65,5 +72,26 @@ Legacy `.tape` files use raw VHS syntax directly. See the [Usage Guide](docs/usa
|
|
|
65
72
|
|
|
66
73
|
*Future: web interaction recording is planned alongside terminal capture.*
|
|
67
74
|
|
|
75
|
+
## Testing Contract
|
|
76
|
+
|
|
77
|
+
Run deterministic package coverage with:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
ace-test ace-demo
|
|
81
|
+
ace-test ace-demo all
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run deterministic feature coverage only when `test/feat/` exists:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
ace-test ace-demo feat
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Run retained workflow scenarios in E2E:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ace-test-e2e ace-demo
|
|
94
|
+
```
|
|
95
|
+
|
|
68
96
|
---
|
|
69
97
|
[Getting Started](docs/getting-started.md) | [Usage Guide](docs/usage.md) | [Handbook - Skills, Agents, Templates](docs/handbook.md) | Part of [ACE](https://github.com/cs3b/ace)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: as-demo-analyze-cast
|
|
3
|
+
description: Analyze failed demo casts and route them to scenario, product, or verifier fixes
|
|
4
|
+
# bundle: wfi://demo/analyze-cast
|
|
5
|
+
# agent: general-purpose
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools:
|
|
8
|
+
- Bash(ace-demo:*)
|
|
9
|
+
- Bash(ace-bundle:*)
|
|
10
|
+
- Read
|
|
11
|
+
argument-hint: "<cast-file> --tape <tape-ref-or-path> [--sandbox-path <path>]"
|
|
12
|
+
last_modified: 2026-04-15
|
|
13
|
+
source: ace-demo
|
|
14
|
+
integration:
|
|
15
|
+
targets:
|
|
16
|
+
- claude
|
|
17
|
+
- codex
|
|
18
|
+
- gemini
|
|
19
|
+
- opencode
|
|
20
|
+
- pi
|
|
21
|
+
skill:
|
|
22
|
+
kind: workflow
|
|
23
|
+
execution:
|
|
24
|
+
workflow: wfi://demo/analyze-cast
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Load and run `ace-bundle wfi://demo/analyze-cast` in the current project, then follow the loaded workflow as the source of truth and execute it end-to-end instead of only summarizing it.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: workflow
|
|
3
|
+
title: Analyze Demo Cast Workflow
|
|
4
|
+
purpose: cast verification triage workflow instruction
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-04-15
|
|
7
|
+
last-checked: 2026-04-15
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Analyze Demo Cast Workflow
|
|
11
|
+
|
|
12
|
+
## Purpose
|
|
13
|
+
|
|
14
|
+
Analyze a recorded `.cast` after demo verification fails, decide whether the problem is the recording scenario, the product, or the verification tooling, and route to the correct next workflow before re-recording.
|
|
15
|
+
|
|
16
|
+
## Instructions
|
|
17
|
+
|
|
18
|
+
1. Run cast verification:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
ace-demo verify <cast-file> --tape <tape-ref-or-path>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If the failed recording preserved a sandbox and the tape uses `assert_commands`, include it:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ace-demo verify <cast-file> --tape <tape-ref-or-path> --sandbox-path <sandbox-path>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Read the generated report under `.ace-local/demo/` or the selected `--report-dir`.
|
|
31
|
+
|
|
32
|
+
3. Route by classification:
|
|
33
|
+
|
|
34
|
+
- `scenario_defect`:
|
|
35
|
+
- fix the tape, camera/viewpoint, setup, or `verify:` contract
|
|
36
|
+
- keep the product code unchanged
|
|
37
|
+
- re-record after updating the demo scenario
|
|
38
|
+
- `product_bug`:
|
|
39
|
+
- treat the cast as a real product/runtime failure
|
|
40
|
+
- run the normal bug analysis/fix workflow for the affected package
|
|
41
|
+
- re-record only after the product fix lands
|
|
42
|
+
- `verification_error`:
|
|
43
|
+
- fix the verifier, cast parser, tape metadata, or recorder/tooling problem
|
|
44
|
+
- re-run verification
|
|
45
|
+
- re-record only if the original cast is unusable or no longer representative
|
|
46
|
+
|
|
47
|
+
4. Do not upload or comment on a PR until `ace-demo verify` returns `pass`.
|
|
48
|
+
|
|
49
|
+
## Success Criteria
|
|
50
|
+
|
|
51
|
+
- Every failed demo recording is triaged through `ace-demo verify`
|
|
52
|
+
- The next action is unambiguous: scenario fix, product bug fix, or verifier/tooling fix
|
|
53
|
+
- Re-recording happens only after the correct upstream issue is fixed
|
|
@@ -29,7 +29,31 @@ Tapes are VHS script files that define terminal sessions: commands to type, timi
|
|
|
29
29
|
|
|
30
30
|
## Instructions
|
|
31
31
|
|
|
32
|
-
1. **
|
|
32
|
+
1. **Define the demo contract before scripting anything**:
|
|
33
|
+
|
|
34
|
+
Capture these decisions in notes or the task/PR before you create a tape:
|
|
35
|
+
|
|
36
|
+
- **Viewpoint**: what exact terminal or tmux client is being recorded
|
|
37
|
+
- **Starting state**: what must already be visible on screen before the trigger action
|
|
38
|
+
- **Trigger action**: the concrete command or interaction that causes the behavior
|
|
39
|
+
- **Visible reaction**: the system state change that must be seen in the recording
|
|
40
|
+
- **End state**: what the reviewer should understand after the recording finishes
|
|
41
|
+
|
|
42
|
+
For demos about tmux, panes, windows, or session routing, the contract must explicitly say which tmux client view is being recorded. A shell transcript alone is not sufficient if the feature is about visible tmux behavior.
|
|
43
|
+
|
|
44
|
+
2. **Keep setup off-camera unless setup is the feature**:
|
|
45
|
+
|
|
46
|
+
Pre-stage long or distracting setup before recording whenever possible:
|
|
47
|
+
|
|
48
|
+
- create fixtures, assignments, and helper scripts before the recording starts
|
|
49
|
+
- start from the state the viewer must recognize
|
|
50
|
+
- only leave setup on camera when the setup step itself is what the demo is proving
|
|
51
|
+
|
|
52
|
+
For tmux fork/window demos, prefer starting in the operator's normal work window and let the recording capture the visible transition into the fork window. Do not begin already attached to the fork window unless the feature is specifically about that attach flow.
|
|
53
|
+
|
|
54
|
+
If the demo is intended to be durable evidence for docs, review, or PR proof, prefer a committed `.tape.yml` / asciinema flow over ad-hoc inline/VHS capture so the resulting `.cast` can be re-verified later.
|
|
55
|
+
|
|
56
|
+
3. **Preview the tape** before writing:
|
|
33
57
|
|
|
34
58
|
```bash
|
|
35
59
|
ace-demo create <name> --dry-run -- "cmd1" "cmd2"
|
|
@@ -37,7 +61,7 @@ Tapes are VHS script files that define terminal sessions: commands to type, timi
|
|
|
37
61
|
|
|
38
62
|
This prints the generated tape content without writing any file.
|
|
39
63
|
|
|
40
|
-
|
|
64
|
+
4. **Create the tape**:
|
|
41
65
|
|
|
42
66
|
```bash
|
|
43
67
|
ace-demo create <name> -- "cmd1" "cmd2"
|
|
@@ -48,13 +72,13 @@ Tapes are VHS script files that define terminal sessions: commands to type, timi
|
|
|
48
72
|
ace-demo create <name> --desc "What this demo shows" --tags "feature,setup" -- "cmd1" "cmd2"
|
|
49
73
|
```
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
5. **Update an existing tape** (overwrite):
|
|
52
76
|
|
|
53
77
|
```bash
|
|
54
78
|
ace-demo create <name> --force -- "cmd1" "cmd2"
|
|
55
79
|
```
|
|
56
80
|
|
|
57
|
-
|
|
81
|
+
6. **Verify** the created tape:
|
|
58
82
|
|
|
59
83
|
```bash
|
|
60
84
|
ace-demo show <name>
|
|
@@ -62,7 +86,7 @@ Tapes are VHS script files that define terminal sessions: commands to type, timi
|
|
|
62
86
|
|
|
63
87
|
This displays metadata and full tape contents.
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
7. **List all available tapes** to confirm visibility:
|
|
66
90
|
|
|
67
91
|
```bash
|
|
68
92
|
ace-demo list
|
|
@@ -86,4 +110,6 @@ Tapes are VHS script files that define terminal sessions: commands to type, timi
|
|
|
86
110
|
|
|
87
111
|
- Tape file created at `.ace/demo/tapes/<name>.tape`
|
|
88
112
|
- `ace-demo show <name>` displays correct metadata and commands
|
|
89
|
-
- `ace-demo list` shows the new tape
|
|
113
|
+
- `ace-demo list` shows the new tape
|
|
114
|
+
- The tape starts from the intended viewer-recognizable state instead of rebuilding irrelevant setup on camera
|
|
115
|
+
- For state-transition demos, the tape visibly shows `before -> trigger -> visible effect -> after`
|
|
@@ -29,10 +29,27 @@ Record terminal demos using `ace-demo record`. Supports two modes: tape-based re
|
|
|
29
29
|
|
|
30
30
|
## Instructions
|
|
31
31
|
|
|
32
|
+
### Validate the Recording Contract First
|
|
33
|
+
|
|
34
|
+
Before choosing tape mode vs inline mode, lock these points:
|
|
35
|
+
|
|
36
|
+
- **What is the user-visible behavior?**
|
|
37
|
+
- **What screen or tmux client must the reviewer be looking at to see it?**
|
|
38
|
+
- **What setup can happen off-camera so the recording starts at the meaningful baseline?**
|
|
39
|
+
|
|
40
|
+
If the feature is about visible tmux behavior such as opening a pane, switching windows, or showing a running fork in the current session:
|
|
41
|
+
|
|
42
|
+
- record the tmux client view that will actually show that transition
|
|
43
|
+
- do not treat a shell transcript as sufficient proof
|
|
44
|
+
- start from the operator's normal work window unless the feature is specifically about attach/reattach behavior
|
|
45
|
+
- prefer showing the transition directly over showing commands that reconstruct the state later
|
|
46
|
+
|
|
32
47
|
### Record from Existing Tape
|
|
33
48
|
|
|
34
49
|
The tape argument accepts a **preset name** (from `ace-demo list`) or a **direct file path** to a `.tape` or `.tape.yml` file.
|
|
35
50
|
|
|
51
|
+
For demos that serve as durable evidence, prefer a committed `.tape.yml` so the generated `.cast` can be verified again with `ace-demo verify`.
|
|
52
|
+
|
|
36
53
|
1. **Find available tapes**:
|
|
37
54
|
|
|
38
55
|
```bash
|
|
@@ -62,6 +79,15 @@ The tape argument accepts a **preset name** (from `ace-demo list`) or a **direct
|
|
|
62
79
|
ace-demo record path/to/tape.tape.yml --output path/to/output.gif
|
|
63
80
|
```
|
|
64
81
|
|
|
82
|
+
5. **Preflight the camera contract for state-transition demos**:
|
|
83
|
+
|
|
84
|
+
Ask these questions before recording:
|
|
85
|
+
|
|
86
|
+
- Does the first part of the recording show the baseline state the reviewer needs?
|
|
87
|
+
- Will the trigger action happen on camera?
|
|
88
|
+
- Will the visible system reaction happen on camera?
|
|
89
|
+
- If the feature is tmux-related, will the recording clearly show the tmux window/pane/session change rather than only a later shell prompt?
|
|
90
|
+
|
|
65
91
|
### Record Inline (Ad-Hoc Commands)
|
|
66
92
|
|
|
67
93
|
1. **Preview** generated tape content:
|
|
@@ -139,8 +165,44 @@ TEST_PATH=ace-bundle ace-demo record test
|
|
|
139
165
|
| `--font-size <n>` | Font size — inline mode (default: 16) |
|
|
140
166
|
| `--playback-speed <speed>` | Postprocess speed: `1x`, `2x`, `4x`, `8x` |
|
|
141
167
|
|
|
168
|
+
## Verification and Recovery
|
|
169
|
+
|
|
170
|
+
For YAML/asciinema demos, recording is not complete when the GIF exists. The cast must pass semantic verification.
|
|
171
|
+
|
|
172
|
+
Use tape `verify:` rules to express:
|
|
173
|
+
- required exported variables (`require_vars`)
|
|
174
|
+
- forbidden output/error signatures (`forbid_output`)
|
|
175
|
+
- final-state assertions (`assert_commands`)
|
|
176
|
+
|
|
177
|
+
After recording:
|
|
178
|
+
|
|
179
|
+
1. If verification passes, continue normally.
|
|
180
|
+
2. If verification fails with `scenario_defect`:
|
|
181
|
+
- inspect the generated report in `.ace-local/demo/`
|
|
182
|
+
- run `ace-demo verify <cast> --tape <tape>` again if needed
|
|
183
|
+
- fix the tape instructions, viewpoint, or setup contract
|
|
184
|
+
- retry recording once
|
|
185
|
+
3. If verification fails with `product_bug`:
|
|
186
|
+
- stop
|
|
187
|
+
- keep the generated report in `.ace-local/demo/`
|
|
188
|
+
- run the cast-analysis workflow and treat it as a real code/runtime bug
|
|
189
|
+
4. Never upload or comment on a PR when verification is not a true pass.
|
|
190
|
+
5. For any non-pass result, branch through `wfi://demo/analyze-cast` before re-recording.
|
|
191
|
+
|
|
192
|
+
### Additional Validation for UX / Tmux Demos
|
|
193
|
+
|
|
194
|
+
Even when the recorder succeeds technically, reject the demo and re-record if any of these are true:
|
|
195
|
+
|
|
196
|
+
- the recording starts after the important state change already happened
|
|
197
|
+
- the recording shows only setup and end state, not the visible transition
|
|
198
|
+
- a tmux/window/pane feature is represented only by status files or textual explanation
|
|
199
|
+
- the viewer cannot identify the operator window, trigger action, and resulting fork/window/pane state from the recording alone
|
|
200
|
+
|
|
142
201
|
## Success Criteria
|
|
143
202
|
|
|
144
203
|
- Recording file produced in `.ace-local/demo/` (plus optional retimed artifact)
|
|
145
|
-
-
|
|
146
|
-
- If `--
|
|
204
|
+
- YAML/asciinema recordings pass semantic verification
|
|
205
|
+
- If `--pr` used: demo uploaded to `demo-assets` release and comment posted on PR only after verification passes
|
|
206
|
+
- If verification fails: error report written to `.ace-local/demo/`
|
|
207
|
+
- If `--dry-run`: preview printed, no side effects
|
|
208
|
+
- For UI/state-transition demos, the recording visibly shows `before -> trigger -> visible effect -> after` from the correct viewer perspective
|
|
@@ -27,6 +27,8 @@ module Ace
|
|
|
27
27
|
lines << "# Scene: #{scene_name}" unless scene_name.to_s.strip.empty?
|
|
28
28
|
|
|
29
29
|
scene.fetch("commands", []).each do |command|
|
|
30
|
+
next unless command["type"]
|
|
31
|
+
|
|
30
32
|
lines << command.fetch("type")
|
|
31
33
|
sleep_value = validate_sleep!(command["sleep"] || default_timeout)
|
|
32
34
|
lines << "sleep #{sleep_value}"
|
|
@@ -6,7 +6,7 @@ module Ace
|
|
|
6
6
|
module Demo
|
|
7
7
|
module Atoms
|
|
8
8
|
module DemoYamlParser
|
|
9
|
-
ALLOWED_ROOT_KEYS = %w[description tags settings setup scenes teardown].freeze
|
|
9
|
+
ALLOWED_ROOT_KEYS = %w[description tags settings setup scenes verify teardown].freeze
|
|
10
10
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
@@ -35,6 +35,7 @@ module Ace
|
|
|
35
35
|
"settings" => normalize_settings(data["settings"], source_path: source_path),
|
|
36
36
|
"setup" => normalize_directives(data["setup"], "setup", source_path: source_path),
|
|
37
37
|
"scenes" => normalize_scenes(data["scenes"], source_path: source_path),
|
|
38
|
+
"verify" => normalize_verify(data["verify"], source_path: source_path),
|
|
38
39
|
"teardown" => normalize_directives(data["teardown"], "teardown", source_path: source_path)
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -164,24 +165,142 @@ module Ace
|
|
|
164
165
|
end
|
|
165
166
|
private_class_method :normalize_scenes
|
|
166
167
|
|
|
168
|
+
def normalize_verify(verify, source_path:)
|
|
169
|
+
return {} if verify.nil?
|
|
170
|
+
raise DemoYamlParseError, "verify must be a map in #{source_path}" unless verify.is_a?(Hash)
|
|
171
|
+
|
|
172
|
+
normalized = {}
|
|
173
|
+
if verify.key?("allow_nonzero_exit")
|
|
174
|
+
normalized["allow_nonzero_exit"] = normalize_boolean(
|
|
175
|
+
verify["allow_nonzero_exit"],
|
|
176
|
+
"verify.allow_nonzero_exit",
|
|
177
|
+
source_path
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
normalized["require_vars"] = normalize_string_list(verify["require_vars"], "verify.require_vars", source_path) if verify.key?("require_vars")
|
|
181
|
+
normalized["require_output"] = normalize_string_list(verify["require_output"], "verify.require_output", source_path) if verify.key?("require_output")
|
|
182
|
+
if verify.key?("require_output_sequence")
|
|
183
|
+
normalized["require_output_sequence"] = normalize_string_list(
|
|
184
|
+
verify["require_output_sequence"],
|
|
185
|
+
"verify.require_output_sequence",
|
|
186
|
+
source_path
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
normalized["forbid_output"] = normalize_string_list(verify["forbid_output"], "verify.forbid_output", source_path) if verify.key?("forbid_output")
|
|
190
|
+
normalized["assert_commands"] = normalize_string_list(verify["assert_commands"], "verify.assert_commands", source_path) if verify.key?("assert_commands")
|
|
191
|
+
normalized
|
|
192
|
+
end
|
|
193
|
+
private_class_method :normalize_verify
|
|
194
|
+
|
|
195
|
+
def normalize_boolean(value, field, source_path)
|
|
196
|
+
return value if value == true || value == false
|
|
197
|
+
|
|
198
|
+
raise DemoYamlParseError, "#{field} must be a boolean in #{source_path}"
|
|
199
|
+
end
|
|
200
|
+
private_class_method :normalize_boolean
|
|
201
|
+
|
|
202
|
+
def normalize_string_list(value, field, source_path)
|
|
203
|
+
raise DemoYamlParseError, "#{field} must be an array in #{source_path}" unless value.is_a?(Array)
|
|
204
|
+
|
|
205
|
+
value.map.with_index do |item, index|
|
|
206
|
+
text = item&.to_s
|
|
207
|
+
if text.nil? || text.strip.empty?
|
|
208
|
+
raise DemoYamlParseError, "#{field}[#{index}] must be a non-empty string in #{source_path}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
text
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
private_class_method :normalize_string_list
|
|
215
|
+
|
|
167
216
|
def normalize_command(command, scene_index, command_index, source_path:)
|
|
168
217
|
unless command.is_a?(Hash)
|
|
169
218
|
raise DemoYamlParseError,
|
|
170
219
|
"scenes[#{scene_index}].commands[#{command_index}] must be a map in #{source_path}"
|
|
171
220
|
end
|
|
172
221
|
|
|
173
|
-
|
|
174
|
-
|
|
222
|
+
normalized = {"sleep" => command["sleep"]&.to_s}.compact
|
|
223
|
+
has_type = !command["type"].to_s.strip.empty?
|
|
224
|
+
has_tmux = command["tmux"].is_a?(Hash)
|
|
225
|
+
|
|
226
|
+
if has_type && has_tmux
|
|
175
227
|
raise DemoYamlParseError,
|
|
176
|
-
"scenes[#{scene_index}].commands[#{command_index}]
|
|
228
|
+
"scenes[#{scene_index}].commands[#{command_index}] cannot define both type and tmux in #{source_path}"
|
|
177
229
|
end
|
|
178
230
|
|
|
179
|
-
|
|
180
|
-
"type"
|
|
181
|
-
|
|
182
|
-
|
|
231
|
+
if has_type
|
|
232
|
+
normalized["type"] = command["type"].to_s
|
|
233
|
+
return normalized
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
if has_tmux
|
|
237
|
+
normalized["tmux"] = normalize_tmux_command(
|
|
238
|
+
command["tmux"],
|
|
239
|
+
scene_index,
|
|
240
|
+
command_index,
|
|
241
|
+
source_path: source_path
|
|
242
|
+
)
|
|
243
|
+
return normalized
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
raise DemoYamlParseError,
|
|
247
|
+
"scenes[#{scene_index}].commands[#{command_index}] must define either type or tmux in #{source_path}"
|
|
183
248
|
end
|
|
184
249
|
private_class_method :normalize_command
|
|
250
|
+
|
|
251
|
+
def normalize_tmux_command(tmux, scene_index, command_index, source_path:)
|
|
252
|
+
directive = tmux.transform_keys(&:to_s)
|
|
253
|
+
action = directive["action"]&.to_s&.strip
|
|
254
|
+
if action.nil? || action.empty?
|
|
255
|
+
raise DemoYamlParseError,
|
|
256
|
+
"scenes[#{scene_index}].commands[#{command_index}].tmux.action is required in #{source_path}"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
allowed = %w[attach detach wait send]
|
|
260
|
+
unless allowed.include?(action)
|
|
261
|
+
raise DemoYamlParseError,
|
|
262
|
+
"Unknown tmux action '#{action}' in #{source_path}. Allowed: #{allowed.join(', ')}"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
normalized = {"action" => action}
|
|
266
|
+
%w[session window pane pattern].each do |field|
|
|
267
|
+
normalized[field] = directive[field].to_s if directive.key?(field)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
case action
|
|
271
|
+
when "attach", "detach"
|
|
272
|
+
normalized["session"] = required_string!(directive, "session", scene_index, command_index, source_path)
|
|
273
|
+
when "wait"
|
|
274
|
+
normalized["for"] = required_string!(directive, "for", scene_index, command_index, source_path)
|
|
275
|
+
normalized["pattern"] = normalized["pattern"] if normalized["pattern"]
|
|
276
|
+
normalized["timeout"] = Float(directive["timeout"]) if directive.key?("timeout")
|
|
277
|
+
when "send"
|
|
278
|
+
command_text = directive["command"]&.to_s&.strip
|
|
279
|
+
key_text = directive["key"]&.to_s&.strip
|
|
280
|
+
if command_text.to_s.empty? == key_text.to_s.empty?
|
|
281
|
+
raise DemoYamlParseError,
|
|
282
|
+
"tmux send must define exactly one of command or key in #{source_path}"
|
|
283
|
+
end
|
|
284
|
+
normalized["command"] = command_text unless command_text.to_s.empty?
|
|
285
|
+
normalized["key"] = key_text unless key_text.to_s.empty?
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
normalized
|
|
289
|
+
rescue ArgumentError, TypeError => e
|
|
290
|
+
raise DemoYamlParseError, "#{e.message} (#{source_path})"
|
|
291
|
+
end
|
|
292
|
+
private_class_method :normalize_tmux_command
|
|
293
|
+
|
|
294
|
+
def required_string!(directive, field, scene_index, command_index, source_path)
|
|
295
|
+
value = directive[field]&.to_s&.strip
|
|
296
|
+
if value.nil? || value.empty?
|
|
297
|
+
raise DemoYamlParseError,
|
|
298
|
+
"scenes[#{scene_index}].commands[#{command_index}].tmux.#{field} is required in #{source_path}"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
value
|
|
302
|
+
end
|
|
303
|
+
private_class_method :required_string!
|
|
185
304
|
end
|
|
186
305
|
end
|
|
187
306
|
end
|
|
@@ -34,11 +34,25 @@ module Ace
|
|
|
34
34
|
raise ArgumentError, "Format 'webm' requires --backend vhs when recording YAML tapes"
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def validate_yaml_backend_capabilities!(backend:, spec:)
|
|
38
|
+
return unless backend == "vhs"
|
|
39
|
+
return unless yaml_uses_tmux_directives?(spec)
|
|
40
|
+
|
|
41
|
+
raise ArgumentError, "Backend 'vhs' does not support tmux directives in YAML tapes; use backend 'asciinema' or remove tmux commands"
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
def validate_raw_tape_backend!(backend:)
|
|
38
45
|
return if backend.nil? || backend == "vhs"
|
|
39
46
|
|
|
40
47
|
raise ArgumentError, "Raw .tape recordings support backend 'vhs' only"
|
|
41
48
|
end
|
|
49
|
+
|
|
50
|
+
def yaml_uses_tmux_directives?(spec)
|
|
51
|
+
Array(spec["scenes"]).any? do |scene|
|
|
52
|
+
Array(scene["commands"]).any? { |command| command.is_a?(Hash) && command["tmux"].is_a?(Hash) }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
private_class_method :yaml_uses_tmux_directives?
|
|
42
56
|
end
|
|
43
57
|
end
|
|
44
58
|
end
|
|
@@ -26,6 +26,8 @@ module Ace
|
|
|
26
26
|
lines << "# Scene: #{scene_name}" unless scene_name.to_s.strip.empty?
|
|
27
27
|
|
|
28
28
|
scene.fetch("commands", []).each do |command|
|
|
29
|
+
next unless command["type"]
|
|
30
|
+
|
|
29
31
|
type_text = command.fetch("type")
|
|
30
32
|
if type_text.include?('"') || type_text.include?("$") || type_text.include?("\\")
|
|
31
33
|
lines << "Type `#{type_text}`"
|
|
@@ -28,6 +28,7 @@ module Ace
|
|
|
28
28
|
allow_nil: false
|
|
29
29
|
)
|
|
30
30
|
RecordOptionValidator.validate_yaml_backend_format!(backend: selected_backend, format: selected_format)
|
|
31
|
+
RecordOptionValidator.validate_yaml_backend_capabilities!(backend: selected_backend, spec: spec)
|
|
31
32
|
|
|
32
33
|
selected_speed = playback_speed.nil? ? settings["playback_speed"] : playback_speed
|
|
33
34
|
selected_speed = Atoms::PlaybackSpeedParser.parse(selected_speed)
|