Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/skills/create-verifier/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ The verifier drives `io.github.stozo04.openloop/.MainActivity` on an emulator li

## Workflow

1. Read `docs/OPERATING_INSTRUCTIONS.md`, `docs/OPENLOOP_INSTRUCTIONS.md`, the matching `.cursor/skills/verify-openloop/features/<feature>.md`, and the relevant product code/strings. Use `.cursor/skills/verify-openloop/helpers/onboarding_loop.py` as the proven reference.
1. Read `docs/OPERATING_INSTRUCTIONS.md`, `docs/OPENLOOP_INSTRUCTIONS.md`, the matching `.claude/skills/verify-openloop/features/<feature>.md`, and the relevant product code/strings. Use `.claude/skills/verify-openloop/helpers/onboarding_loop.py` as the proven reference.
2. Derive observable acceptance criteria from the shipped product: precondition, entry point, exact must-have/must-not-have UI, user actions, persisted/resulting state, and any required non-UI proof such as logcat or a file.
3. Create `.cursor/skills/verify-openloop/helpers/<feature>_loop.py` with Python's standard library. Reuse an existing helper when one fits; extract shared code only after two real loops demonstrate material duplication.
4. Update the matching feature recipe with the direct run command. Run `python scripts/sync-harness-skills.py --fix --from cursor`, then `--check`.
3. Create `.claude/skills/verify-openloop/helpers/<feature>_loop.py` with Python's standard library. Reuse an existing helper when one fits; extract shared code only after two real loops demonstrate material duplication.
4. Update the matching feature recipe with the direct run command. Run `python scripts/sync-harness-skills.py --fix` (it takes the direction from git and retargets each copy's own paths), then `--check`.
5. Build the current debug APK if needed and run the new loop on a booted emulator. Do not report completion from syntax checks or exit code alone; independently confirm its final PASS marker and evidence artifacts.

`scripts/run-verification-loops.py --changed` discovers shipped `*_loop.py` files automatically, so there is no registry or roadmap to update.
Expand Down
16 changes: 15 additions & 1 deletion .claude/skills/harness-sync/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: harness-sync
description: Check and repair byte-drift between the three LLM harness skill trees — `.claude/skills/`, `.cursor/skills/` and `.codex/skills/` — which the Definition of Done (M5) requires to be byte-identical. Use this skill whenever you edit, add, delete or rename ANYTHING under any of those three directories, and whenever the user says "sync harness", "sync the skills", "/harness-sync", "harness drift", "gate 6d", "M5", "sweep gate 6d is red", "the skill trees have drifted", "propagate this skill to the other LLMs", or asks whether the three harness folders match. Also use it proactively before committing a skill change and when a pre-PR sweep or CI run reports the "Harness skill trees byte-identical" gate as failing — a skills edit that reaches only one harness is the exact failure this catches.
description: Check and repair byte-drift between the three LLM harness skill trees — `.claude/skills/`, `.cursor/skills/` and `.codex/skills/` — which the Definition of Done (M5) requires to be identical apart from each copy's pointer at its own tree. Use this skill whenever you edit, add, delete or rename ANYTHING under any of those three directories, and whenever the user says "sync harness", "sync the skills", "/harness-sync", "harness drift", "gate 6d", "M5", "sweep gate 6d is red", "the skill trees have drifted", "propagate this skill to the other LLMs", or asks whether the three harness folders match. Also use it proactively before committing a skill change and when a pre-PR sweep or CI run reports the "Harness skill trees byte-identical" gate as failing — a skills edit that reaches only one harness is the exact failure this catches.
---

# harness-sync — keep the three harness skill trees identical
Expand Down Expand Up @@ -79,6 +79,20 @@ must NOT be synced:
They are Claude-only by design and live outside `skills/`, which is why the script never sees
them.

One difference inside the compared files IS allowed, and only one: **a skill's pointer at its own
tree**. A recipe that points at `<harness>/skills/verify-openloop/helpers/onboarding_loop.py` names
a different directory in each copy, because otherwise two of the three send their LLM to a path it
cannot read (owner instruction, 2026-08-31). The leading `.claude` / `.cursor` / `.codex` of such a
path is compared as one token, and `--fix` rewrites it for each destination — so you still edit one
tree and propagate. Three rules keep it honest:

- Each copy must point at **itself**. A `.cursor` copy naming the `.codex` tree is drift, and the
gate says so.
- Only a path that continues past the tree root counts. Prose listing the three directories — the
`## Scope` heading below does it — is a list, not a pointer, and is compared literally.
- Anything outside a harness's `skills/` (`~/.cursor/mcp.json`, `.claude/commands/`) belongs to one
harness for real and stays literal in all three copies.

If you are adding a fourth harness, create `.<name>/skills/`, add the name to `HARNESSES` in the
script, and add the allowlist entries in `.github/workflows/doc-layout.yml` and
`docs/README.md` § Enforcement — in the same PR, per Definition of Done M1.
Expand Down
10 changes: 5 additions & 5 deletions .claude/skills/verify-openloop/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Isolation: **one emulator**. Parallel AVDs fight over host CPU and codecs and in
$env:JAVA_HOME = "C:\Program Files\Android\Android Studio\jbr"
$env:VERIFY_RUN_ID = Get-Date -Format "yyyyMMdd_HHmmss"
# Start one AVD first (Pixel_6 / Pixel_8 / Pixel_8_API34 are the known ones).
pwsh .cursor/skills/verify-openloop/helpers/control.ps1 launch
pwsh .claude/skills/verify-openloop/helpers/control.ps1 launch
```

Ready when `control.ps1 doctor` prints `ok` and `control.ps1 dump` shows one of:
Expand All @@ -49,7 +49,7 @@ If launch fails, stop. Do not tap some other OpenLoop build (`com.OpenLoop.app`
Read-only. Run before the first drive, after any failed drive, and whenever the instance looks wrong.

```powershell
pwsh .cursor/skills/verify-openloop/helpers/control.ps1 doctor
pwsh .claude/skills/verify-openloop/helpers/control.ps1 doctor
```

Must confirm: serial is an emulator (or an explicitly allowed test device), `io.github.stozo04.openloop` is installed, `versionName`/`versionCode` match this checkout (`1.0.49` / `49` on the branch this skill was written against — re-read `app/build.gradle.kts` if they drifted), and `adb get-state` is `device`.
Expand All @@ -61,8 +61,8 @@ If doctor fails, relaunch or abort. Never drive an instance you did not start.
Harness: `uiauto.ps1` (uiautomator dump → tap by text or content-desc). Screenshots hit a per-session image limit; the dump is the eyes.

```powershell
pwsh .cursor/skills/verify-openloop/helpers/control.ps1 dump
pwsh .cursor/skills/verify-openloop/helpers/control.ps1 tap -Label "Start recording"
pwsh .claude/skills/verify-openloop/helpers/control.ps1 dump
pwsh .claude/skills/verify-openloop/helpers/control.ps1 tap -Label "Start recording"
```

Prefer these handles, in order:
Expand Down Expand Up @@ -99,7 +99,7 @@ Name artifacts `$VERIFY_EVIDENCE_DIR/<feature-id>/`.
Kill what this run started. Never `pkill` / `adb shell pkill` by name.

```powershell
pwsh .cursor/skills/verify-openloop/helpers/control.ps1 cleanup
pwsh .claude/skills/verify-openloop/helpers/control.ps1 cleanup
```

Force-stops `io.github.stozo04.openloop` on **this serial only**. Leaves the APK installed. Does not delete `$VERIFY_EVIDENCE_DIR`. Does not wipe gallery clips unless the recipe says so. Does not reset onboarding unless you ran `.claude/skills/reset-storage/`.
Expand Down
50 changes: 27 additions & 23 deletions .claude/skills/verify-openloop/features/INVENTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@ Does **not** need to stay perfectly current between audits — the [README compl

**PRDs are optional.** Prefer `strings.xml` + UI chrome. Add a PRD column only when a PRD exists.

| Surface (user-visible) | Source hint | Status | Feature file |
| ------------------------------------ | ---------------------------- | ------------------ | -------------------------------------------------- |
| Onboarding `LET'S GO!` | strings / OnboardingScreen | automated | [onboarding.md](./onboarding.md) |
| Record / stop video | `Start recording` | mapped | [record-clip.md](./record-clip.md) |
| Lenses drawer + catalogue | `Lenses and Photo Booth` | mapped | [lenses.md](./lenses.md) |
| Multi-face (1–2) lens | FaceRoster / live preview | folded into lenses | [lenses.md](./lenses.md) |
| Photo stills mode | `Camera` / `Take photo` | mapped | [photo-capture.md](./photo-capture.md) |
| Photo booth | booth tab / countdown | mapped | [photo-booth.md](./photo-booth.md) |
| Pinch zoom | zoom chip | mapped | [pinch-zoom.md](./pinch-zoom.md) |
| Import from Photos | `Import a video` | mapped | [import-video.md](./import-video.md) |
| Gallery browse / play | Gallery | mapped | [gallery.md](./gallery.md) |
| Gallery SEND | `SEND` | folded into share | [share-and-library.md](./share-and-library.md) |
| Save to Photos + share sheet | `Saved to Photos` | mapped | [share-and-library.md](./share-and-library.md) |
| Trim | Trim screen / tab | mapped | [edit-trim.md](./edit-trim.md) |
| Speed constant + curve | Speed tab | mapped | [edit-speed.md](./edit-speed.md) |
| Loop direction | Loop tab | mapped | [edit-loop.md](./edit-loop.md) |
| Filter / Looks | Filter tab | mapped | [edit-filter.md](./edit-filter.md) |
| Delete in-progress clip | toolbar Delete | mapped | [edit-delete.md](./edit-delete.md) |
| Save boomerang | `Save boomerang` | mapped | [edit-save.md](./edit-save.md) |
| Full edit → save path | run-e2e | orchestrator | [edit-and-save.md](./edit-and-save.md) |
| Permission rationale / denied | MainActivity strings | out of scope | support chrome — drive only when blocking a recipe |
| In-app review / Play update snackbar | review/update packages | out of scope | system/Play prompts, not core verify map |
| Debug report share | reverse-failed / save-failed | out of scope | support path |
| Surface (user-visible) | Source hint | Status | Feature file |
| ------------------------------------ | ---------------------------- | -------------------- | -------------------------------------------------- |
| Onboarding `LET'S GO!` | strings / OnboardingScreen | automated 2026-08-31 | [onboarding.md](./onboarding.md) |
| Record / stop video | `Start recording` | automated 2026-08-31 | [record-clip.md](./record-clip.md) |
| Lenses drawer + catalogue | `Lenses and Photo Booth` | mapped | [lenses.md](./lenses.md) |
| Multi-face (1–2) lens | FaceRoster / live preview | folded into lenses | [lenses.md](./lenses.md) |
| Photo stills mode | `Camera` / `Take photo` | mapped | [photo-capture.md](./photo-capture.md) |
| Photo booth | booth tab / countdown | mapped | [photo-booth.md](./photo-booth.md) |
| Pinch zoom | zoom chip | mapped | [pinch-zoom.md](./pinch-zoom.md) |
| Import from Photos | `Import a video` | mapped | [import-video.md](./import-video.md) |
| Gallery browse / play | Gallery | mapped | [gallery.md](./gallery.md) |
| Gallery SEND | `SEND` | folded into share | [share-and-library.md](./share-and-library.md) |
| Save to Photos + share sheet | `Saved to Photos` | mapped | [share-and-library.md](./share-and-library.md) |
| Trim | Trim screen / tab | mapped | [edit-trim.md](./edit-trim.md) |
| Speed constant + curve | Speed tab | mapped | [edit-speed.md](./edit-speed.md) |
| Loop direction | Loop tab | mapped | [edit-loop.md](./edit-loop.md) |
| Filter / Looks | Filter tab | mapped | [edit-filter.md](./edit-filter.md) |
| Delete in-progress clip | toolbar Delete | mapped | [edit-delete.md](./edit-delete.md) |
| Save boomerang | `Save boomerang` | mapped | [edit-save.md](./edit-save.md) |
| Full edit → save path | run-e2e | orchestrator | [edit-and-save.md](./edit-and-save.md) |
| Permission rationale / denied | MainActivity strings | out of scope | support chrome — drive only when blocking a recipe |
| In-app review / Play update snackbar | review/update packages | out of scope | system/Play prompts, not core verify map |
| Debug report share | reverse-failed / save-failed | out of scope | support path |

When you find a new shipped control in `strings.xml` or chrome that is not listed, add a row as `missing` until a feature file exists.

`automated <date>` means a `*_loop.py` verifier drives that surface on an emulator and was seen
passing on that date — not that the recipe merely mentions one. `mapped` is a written recipe with
no autonomous check behind it yet.
2 changes: 1 addition & 1 deletion .claude/skills/verify-openloop/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This directory is the maintained source for verifying the user-facing behavior o

- One emulator attached. Serial in `VERIFY_SERIAL` if more than one `adb devices` line is `device`.
- Debug APK from **this** checkout installed (`io.github.stozo04.openloop`).
- `pwsh .cursor/skills/verify-openloop/helpers/control.ps1 doctor` passes.
- `pwsh .claude/skills/verify-openloop/helpers/control.ps1 doctor` passes.
- Camera permission granted (`control.ps1 grant-camera` or install `-g`).
- Never drive a physical phone unless `VERIFY_ALLOW_DEVICE=1`.
- Never drive `com.OpenLoop.app`.
Expand Down
28 changes: 28 additions & 0 deletions .claude/skills/verify-openloop/features/record-clip.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ From the camera viewfinder the user taps the shutter to start a video (up to 30
- Confirm capture mode is **Video** (`capture_mode_selector`, label `Video`) not Camera/stills and not Photo Booth.
- Tap the large shutter at the bottom center.

## Autonomous check

Run it from the repository root:

```powershell
python .claude/skills/verify-openloop/helpers/record_clip_loop.py
```

`python scripts/run-verification-loops.py --changed` runs it alongside every other loop. Both take
`VERIFY_SERIAL` when more than one emulator is online and `VERIFY_EVIDENCE_DIR` for the artifacts.

It installs the current debug APK, grants CAMERA, and drives all three outcomes of a shutter tap:
a sub-400 ms double tap (stays on camera, `That was quick!` snackbar, no clip), a few seconds
(Trim opens, scratch clip on disk, `Capture finalized (Nms)` in logcat), and a recording left to
run out (the 30 s cap finalizes it with no stop tap). Every countdown sample is asserted as
`<seconds>s / 30s` — never a `mm:ss` clock. Both clips are thrown away through the Discard dialog,
so nothing is saved and the gallery is untouched.

Expect roughly **75 seconds** on a healthy Pixel_8_API34 AVD, where the cap produces a 31.2 s clip.
The elapsed counter accumulates 33 ms per tick instead of reading a clock, so a device that cannot
hold the cadence overruns: the same AVD under host memory pressure took 219 s and produced a
**143 s** clip for the same 30 s cap. The cap is therefore asserted as "no stop tap, no error
finalize, Trim opened, clip ≥ 25 s" — never a wall-clock ceiling, and never how high the chip had
climbed at the last dump, which measures the polling interval rather than the product.

## Driving it with control.ps1

Preconditions:
Expand All @@ -38,4 +63,7 @@ Preconditions:
- Import (`Import a video` / gallery empty `…or import one`) is a different entry into Trim — see [import-video](./import-video.md). Do not count import as `record-clip`.
- Active [lenses](./lenses.md) bake into the recording; prove lens UI separately if that is the claim.
- Permission rationale can sit on top of the shutter. Dump first.
- The too-short snackbar covers the shutter row: while it is up, `Start recording` / `Flip Camera` are not in the dump at all. Prove "still on camera" with the mode selector, then re-check the shutter once it clears.
- A uiautomator dump takes seconds and the snackbar lasts four, so polling with dumps loses that race. Wait on logcat (`Video burst recording failed` / `below the 400ms minimum`), then dump once.
- Under host memory pressure the AVD's own system ANRs (`System UI isn't responding`, package `android`, in the dump) and every recipe here fails at the first step. That is the host, not the app — free memory and cold-boot the AVD (`-no-snapshot-load`); `adb reboot` restores the same broken state.
- Front/back flip (`Flip Camera`) does not by itself prove a recording.
Loading
Loading