From 77bf9717dc1efaa45ed685b9d5a348c605d04ce8 Mon Sep 17 00:00:00 2001 From: alexanderbrunker-star Date: Thu, 23 Apr 2026 21:36:20 +0200 Subject: [PATCH] chore: harden verification architecture --- Makefile | 17 +- README.md | 11 +- docs/guides/github-workflow.md | 5 +- .../2026-04-23-verification-architecture.md | 160 ++++++++++++++++++ ...-04-23-verification-architecture-design.md | 52 ++++++ scripts/check_warnings.sh | 138 ++++++++++++++- 6 files changed, 369 insertions(+), 14 deletions(-) create mode 100644 docs/superpowers/plans/2026-04-23-verification-architecture.md create mode 100644 docs/superpowers/specs/2026-04-23-verification-architecture-design.md diff --git a/Makefile b/Makefile index c8f470c..ddd5c91 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help chrome safari ios mac mac-smoke android test-core-js test-core-swift test-chrome verify clean-dist chrome-build-assets +.PHONY: help chrome safari ios mac mac-smoke android test test-core-js test-core-swift test-chrome test-android test-ios verify clean-dist chrome-build-assets CHROME_DIR := sonicflow_app/chrome-extension SAFARI_PROJECT := sonicflow_app/safari-extension/FlowTones/FlowTones.xcodeproj @@ -16,9 +16,12 @@ help: @echo " make mac Build macOS app target" @echo " make mac-smoke Build and launch the macOS menu-bar app" @echo " make android Build Android debug APK" + @echo " make test Run focused test suites across configured platforms" @echo " make test-core-js Run JS core tests" @echo " make test-core-swift Run Swift core tests" @echo " make test-chrome Run Chrome extension tests" + @echo " make test-android Run Android unit tests when SDK/Java are configured" + @echo " make test-ios Run iOS app tests when a simulator is available" @echo " make verify Run warning audit across supported platforms" @echo " make clean-dist Remove dist artifacts" @@ -63,6 +66,18 @@ test-core-swift: test-chrome: cd $(CHROME_DIR) && npm ci && npm test +test-android: + @if [ -x $(ANDROID_APP_DIR)/gradlew ] && { [ -n "$${ANDROID_HOME:-$${ANDROID_SDK_ROOT:-}}" ] || [ -f $(ANDROID_APP_DIR)/local.properties ]; } && command -v java >/dev/null 2>&1; then \ + cd $(ANDROID_APP_DIR) && ./gradlew testDebugUnitTest; \ + else \ + echo 'Skipping Android unit tests because Android SDK/Java are not configured.'; \ + fi + +test-ios: + ./scripts/check_warnings.sh --ios-tests + +test: test-core-js test-core-swift test-chrome test-android test-ios + verify: ./scripts/check_warnings.sh diff --git a/README.md b/README.md index f08e53a..cf26152 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ make ios make mac make mac-smoke make android +make test make verify ``` @@ -113,16 +114,19 @@ make verify ## Development Checks -- Full warning audit: `make verify` +- Fast cross-platform unit checks: `make test` +- Full merge gate with warning audit: `make verify` - JS core tests: `make test-core-js` - Swift core tests: `make test-core-swift` - Chrome extension tests: `make test-chrome` +- Android unit tests: `make test-android` +- iOS app tests: `make test-ios` - Chrome popup upgrade slice: `cd sonicflow_app/chrome-extension && node --test popup.test.js` - Android session model slice: `cd sonicflow_app/android-app && ./gradlew --console=plain :app:testDebugUnitTest --tests 'com.sonicflow.app.ui.FlowTonesViewModelTest'` -- iOS compile gate for FlowTones UI/runtime slices: `xcodebuild build-for-testing -project sonicflow_app/ios-app/FlowTones.xcodeproj -scheme FlowTones -destination 'platform=iOS Simulator,id=FBAF6087-CB95-4D6D-B304-99A2F6A21440'` +- iOS focused test gate: `make test-ios` - macOS menu-bar smoke gate: `make mac-smoke` -The warning audit runs cross-platform checks and skips Android only when SDK/Java prerequisites are not configured locally. +The warning audit runs cross-platform tests/builds and skips Android only when SDK/Java prerequisites are not configured locally. iOS app tests run when a compatible simulator destination is available. ## Workflow (Linear-First) @@ -162,4 +166,3 @@ Parallel work is supported via agents for independent tickets, with one branch/P - Safari behavior can differ between iOS Safari and macOS Safari due to Web Extension API differences. - Browser shells expose FlowTones-style controls, but they do not yet offer native offline render/export or cache flows. - Android currently carries `durationMinutes`, `ambientMix`, and `pulseDepth` through the session model and UI, but the underlying audio engine is not yet feature-parity with the Apple-native runtime. -- The iOS targeted simulator `test` action can still be sticky in local CLI runs even when `build-for-testing` succeeds; use the compile gate above as the current reliable verification baseline for new iOS slices. diff --git a/docs/guides/github-workflow.md b/docs/guides/github-workflow.md index 0a3e811..3a3b53f 100644 --- a/docs/guides/github-workflow.md +++ b/docs/guides/github-workflow.md @@ -24,10 +24,11 @@ - `SF-1`: `cd sonicflow_app/core-js && npm test` - `SF-2`: `cd sonicflow_app/core-swift && swift test` - `SF-3`: `cd sonicflow_app/android-app && ./gradlew assembleDebug` -- All PRs touching shipped code: `./scripts/check_warnings.sh` +- Fast local confidence before opening a PR: `make test` +- All PRs touching shipped code: `make verify` ## Warning Standard -- PRs are expected to merge with zero project warnings across JS, Swift, iOS, macOS, Chrome extension, and Android app builds. +- PRs are expected to merge with zero project warnings across JS, Swift, iOS, macOS, Chrome extension, and Android app test/build surfaces. - `./scripts/check_warnings.sh` is the shared audit entry point for local verification and CI enforcement. - Toolchain-only noise that cannot be acted on in repo code should be explicitly filtered in the script instead of being ignored in review. diff --git a/docs/superpowers/plans/2026-04-23-verification-architecture.md b/docs/superpowers/plans/2026-04-23-verification-architecture.md new file mode 100644 index 0000000..d7ef323 --- /dev/null +++ b/docs/superpowers/plans/2026-04-23-verification-architecture.md @@ -0,0 +1,160 @@ +# Verification Architecture Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Strengthen SonicFlow's local and merge verification path so existing tests are first-class and warning policy is covered by tests. + +**Architecture:** Keep `scripts/check_warnings.sh` as the merge gate, extract its warning classifier into a callable function, and add test-mode coverage before expanding the command sequence. Use the Makefile as the friendly entry point for focused platform tests and full verification. + +**Tech Stack:** Bash, Python 3, GNU Make, Node test runner, SwiftPM, xcodebuild, Gradle + +--- + +## File Structure + +- Modify: `scripts/check_warnings.sh` + - Owns repo-wide verification, warning classification, optional simulator detection, and self-test mode. +- Modify: `Makefile` + - Exposes focused test targets and keeps `make verify` as the full merge gate. +- Modify: `README.md` + - Documents the updated local test and verification commands. +- Modify: `docs/guides/github-workflow.md` + - Aligns merge rules with the stronger verification gate. + +### Task 1: Add warning-classifier self-test support + +**Files:** +- Modify: `scripts/check_warnings.sh` + +- [ ] **Step 1: Write the failing test mode** + +Add a `--self-test` mode to `scripts/check_warnings.sh` that writes sample logs to a temporary directory and asserts that the classifier returns: + +```text +empty output for accepted AppIntents metadata noise +empty output for accepted Gradle deprecation summary +non-empty output for a real Swift warning +non-empty output for a real Kotlin warning line +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `./scripts/check_warnings.sh --self-test` + +Expected: fail because `--self-test` is not implemented on the current branch. + +- [ ] **Step 3: Implement the classifier function** + +Move the existing inline Python warning classifier behind a `collect_warnings()` function that can be called both by `check_step()` and by `--self-test`. + +- [ ] **Step 4: Run the self-test** + +Run: `./scripts/check_warnings.sh --self-test` + +Expected: pass with a concise self-test confirmation. + +### Task 2: Expand the merge verification gate + +**Files:** +- Modify: `scripts/check_warnings.sh` + +- [ ] **Step 1: Add Chrome extension tests to the audit** + +Add a `chrome_test` step: + +```bash +run_step chrome_test bash -lc "cd '$ROOT_DIR/sonicflow_app/chrome-extension' && npm test" +check_step chrome_test +``` + +- [ ] **Step 2: Add Android unit tests when prerequisites exist** + +Inside the existing Android prerequisite branch, run: + +```bash +run_step android_unit_tests bash -lc "cd '$ROOT_DIR/sonicflow_app/android-app' && ./gradlew testDebugUnitTest" +check_step android_unit_tests +run_step android_build bash -lc "cd '$ROOT_DIR/sonicflow_app/android-app' && ./gradlew assembleDebug" +check_step android_build +``` + +- [ ] **Step 3: Add iOS simulator tests when a destination exists** + +Add helper logic that uses `xcodebuild -showdestinations` to find an `iPhone 17 Pro` simulator if present, otherwise the first available iOS simulator. If no simulator exists, print a skip reason. When one exists, run: + +```bash +run_step ios_tests xcodebuild -project "$ROOT_DIR/sonicflow_app/ios-app/FlowTones.xcodeproj" -scheme FlowTones -configuration Debug -destination "$IOS_TEST_DESTINATION" CODE_SIGNING_ALLOWED=NO test +check_step ios_tests +``` + +- [ ] **Step 4: Run the full gate** + +Run: `make verify` + +Expected: all configured steps pass with accepted toolchain-only warnings filtered. + +### Task 3: Expose focused test targets + +**Files:** +- Modify: `Makefile` + +- [ ] **Step 1: Add `test-android`, `test-ios`, and aggregate `test` targets** + +Add phony targets that run Android unit tests when Gradle is available, iOS simulator tests through the verification script destination helper, and all unit-level test suites together. + +- [ ] **Step 2: Run the aggregate target** + +Run: `make test` + +Expected: focused unit-level suites pass or skip only when prerequisites are missing. + +### Task 4: Update contributor documentation + +**Files:** +- Modify: `README.md` +- Modify: `docs/guides/github-workflow.md` + +- [ ] **Step 1: Document focused and full commands** + +Update the command lists so contributors know `make test` is the fast cross-platform test command and `make verify` is the full merge gate. + +- [ ] **Step 2: Confirm docs mention the same target names as the Makefile** + +Run: `rg -n "make test|make verify|test-android|test-ios" README.md docs/guides/github-workflow.md Makefile` + +Expected: output shows matching target names in docs and Makefile. + +### Task 5: Final validation and publish + +**Files:** +- All modified files + +- [ ] **Step 1: Run formatting/diff sanity** + +Run: `git diff --check` + +Expected: no whitespace errors. + +- [ ] **Step 2: Run focused tests** + +Run: `make test` + +Expected: all configured focused tests pass. + +- [ ] **Step 3: Run full verification** + +Run: `make verify` + +Expected: all configured verification steps pass. + +- [ ] **Step 4: Commit and publish** + +Run: + +```bash +git add scripts/check_warnings.sh Makefile README.md docs/guides/github-workflow.md docs/superpowers/specs/2026-04-23-verification-architecture-design.md docs/superpowers/plans/2026-04-23-verification-architecture.md +git commit -m "chore: harden verification architecture" +git push -u origin codex/optimize-code-test-architecture +``` + +Open a draft pull request against `main` titled `[codex] Harden verification architecture`. diff --git a/docs/superpowers/specs/2026-04-23-verification-architecture-design.md b/docs/superpowers/specs/2026-04-23-verification-architecture-design.md new file mode 100644 index 0000000..2370523 --- /dev/null +++ b/docs/superpowers/specs/2026-04-23-verification-architecture-design.md @@ -0,0 +1,52 @@ +# Verification Architecture Design + +## Summary + +Harden SonicFlow's repo-wide verification path so a branch cannot look merge-ready while skipping important test surfaces. The scope is deliberately limited to test and verification architecture: no product runtime behavior, UI behavior, audio behavior, or brand behavior changes. + +## Current Context + +The monorepo has working platform-level checks, but they are unevenly exposed: + +- `make test-core-js`, `make test-chrome`, and `make test-core-swift` run focused test suites. +- Android unit tests and iOS app tests exist, but they are not first-class Makefile targets. +- `make verify` builds several platform surfaces and runs shared core tests, but it does not run Chrome extension tests, Android unit tests, or iOS app tests. +- `scripts/check_warnings.sh` owns the merge warning policy, but its warning classifier is embedded inline and has no direct test coverage. + +## Design + +The verification architecture will use `scripts/check_warnings.sh` as the single repo-wide merge gate and make its behavior explicit. + +The script will: + +- keep the existing step runner and warning audit model +- expose a test-only mode for the warning classifier +- run Chrome extension tests after the Chrome extension build +- run Android unit tests when Android prerequisites are configured +- run iOS app tests when a compatible simulator destination is available +- keep iOS and macOS builds in the full audit +- continue filtering known toolchain-only warnings already accepted by the repo + +The Makefile will become the discoverable local interface for the same surfaces: + +- `make test-core-js` +- `make test-core-swift` +- `make test-chrome` +- `make test-android` +- `make test-ios` +- `make test` +- `make verify` + +## Test Strategy + +Add a shell test suite for `scripts/check_warnings.sh` that validates the warning classifier before expanding the verification flow. The test will exercise accepted toolchain noise, real warning detection, and mixed logs. + +Then run the focused targets and the full `make verify` command to prove the repo still passes the stronger gate. + +## Acceptance + +- `scripts/check_warnings.sh --self-test` passes. +- `make test` runs the local unit-level test suite across JS, Chrome, Swift, Android when available, and iOS when a simulator is available. +- `make verify` includes the unit-level surfaces plus existing build and warning checks. +- Documentation tells contributors which command to run for focused tests and merge verification. +- The branch is pushed to GitHub as a draft PR after local validation. diff --git a/scripts/check_warnings.sh b/scripts/check_warnings.sh index c8cf811..d20a5f3 100755 --- a/scripts/check_warnings.sh +++ b/scripts/check_warnings.sh @@ -2,6 +2,8 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IOS_PROJECT="$ROOT_DIR/sonicflow_app/ios-app/FlowTones.xcodeproj" +IOS_SCHEME="FlowTones" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT @@ -39,6 +41,7 @@ warning_patterns = [ ignore_patterns = [ re.compile(r"Metadata extraction skipped\. No AppIntents\.framework dependency found\."), re.compile(r"Deprecated Gradle features were used in this build, making it incompatible with Gradle 9\.0\."), + re.compile(r"The following options were not recognized by any processor: '\[dagger\."), ] for line in log_path.read_text(errors="ignore").splitlines(): @@ -49,6 +52,66 @@ for line in log_path.read_text(errors="ignore").splitlines(): PY } +assert_warnings() { + local name="$1" + local expected="$2" + local log_file="$3" + local warnings + warnings="$(collect_warnings "$log_file")" + + case "$expected" in + none) + if [[ -n "$warnings" ]]; then + echo "Self-test failed: ${name} produced unexpected warnings" + printf '%s\n' "$warnings" + exit 1 + fi + ;; + some) + if [[ -z "$warnings" ]]; then + echo "Self-test failed: ${name} did not produce expected warnings" + exit 1 + fi + ;; + *) + echo "Unknown self-test expectation: ${expected}" + exit 1 + ;; + esac +} + +run_self_test() { + local appintents_log="$TMP_DIR/appintents.log" + local gradle_log="$TMP_DIR/gradle.log" + local kapt_log="$TMP_DIR/kapt.log" + local swift_warning_log="$TMP_DIR/swift-warning.log" + local kotlin_warning_log="$TMP_DIR/kotlin-warning.log" + + printf '%s\n' \ + "2026-04-23 appintentsmetadataprocessor[1:1] warning: Metadata extraction skipped. No AppIntents.framework dependency found." \ + >"$appintents_log" + printf '%s\n' \ + "Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0." \ + >"$gradle_log" + printf '%s\n' \ + "warning: The following options were not recognized by any processor: '[dagger.fastInit, kapt.kotlin.generated]'" \ + >"$kapt_log" + printf '%s\n' \ + "/tmp/File.swift:12:8: warning: variable 'value' was never mutated" \ + >"$swift_warning_log" + printf '%s\n' \ + "w: /tmp/File.kt: (7, 13): Parameter 'unused' is never used" \ + >"$kotlin_warning_log" + + assert_warnings "accepted AppIntents metadata warning" none "$appintents_log" + assert_warnings "accepted Gradle deprecation summary" none "$gradle_log" + assert_warnings "accepted KAPT processor options warning" none "$kapt_log" + assert_warnings "real Swift warning" some "$swift_warning_log" + assert_warnings "real Kotlin warning" some "$kotlin_warning_log" + + echo "check_warnings self-test passed" +} + check_step() { local name="$1" local log_file="$TMP_DIR/${name}.log" @@ -62,33 +125,94 @@ check_step() { fi } +find_ios_test_destination() { + local destinations + destinations="$(xcodebuild -project "$IOS_PROJECT" -scheme "$IOS_SCHEME" -showdestinations 2>/dev/null || true)" + + DESTINATIONS="$destinations" python3 - <<'PY' +import re +import os + +preferred = None +fallback = None + +for line in os.environ["DESTINATIONS"].splitlines(): + if "platform:iOS Simulator" not in line or "placeholder" in line: + continue + + identifier = re.search(r"id:([^,}]+)", line) + if not identifier: + continue + + destination = f"id={identifier.group(1).strip()}" + + if "name:iPhone 17 Pro" in line: + preferred = destination + break + + if fallback is None: + fallback = destination + +print(preferred or fallback or "") +PY +} + +run_ios_tests_if_available() { + local destination + destination="$(find_ios_test_destination)" + + if [[ -z "$destination" ]]; then + echo "==> ios_tests" + echo "Skipping iOS tests because no iOS simulator destination is available." + return 0 + fi + + run_step ios_tests xcodebuild -project "$IOS_PROJECT" -scheme "$IOS_SCHEME" -configuration Debug -destination "$destination" CODE_SIGNING_ALLOWED=NO test + check_step ios_tests +} + +if [[ "${1:-}" == "--self-test" ]]; then + run_self_test + exit 0 +fi + +if [[ "${1:-}" == "--ios-tests" ]]; then + run_ios_tests_if_available + exit 0 +fi + +run_self_test + run_step core_js npm --prefix "$ROOT_DIR/sonicflow_app/core-js" test check_step core_js run_step chrome_build bash -lc "cd '$ROOT_DIR/sonicflow_app/chrome-extension' && npm ci && npm run build" check_step chrome_build +run_step chrome_test bash -lc "cd '$ROOT_DIR/sonicflow_app/chrome-extension' && npm test" +check_step chrome_test + run_step core_swift swift test --package-path "$ROOT_DIR/sonicflow_app/core-swift" check_step core_swift -run_step ios_build xcodebuild -project "$ROOT_DIR/sonicflow_app/ios-app/FlowTones.xcodeproj" -scheme FlowTones -configuration Debug -destination "generic/platform=iOS Simulator" CODE_SIGNING_ALLOWED=NO build +run_step ios_build xcodebuild -project "$IOS_PROJECT" -scheme "$IOS_SCHEME" -configuration Debug -destination "generic/platform=iOS Simulator" CODE_SIGNING_ALLOWED=NO build check_step ios_build +run_ios_tests_if_available + run_step mac_build xcodebuild -project "$ROOT_DIR/sonicflow_app/safari-extension/FlowTones/FlowTones.xcodeproj" -scheme "FlowTones (macOS)" -configuration Debug -destination "generic/platform=macOS" CODE_SIGNING_ALLOWED=NO build check_step mac_build -has_android_sdk_config=false -if [[ -n "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" || -f "$ROOT_DIR/sonicflow_app/android-app/local.properties" ]]; then - has_android_sdk_config=true -fi - -if [[ "$has_android_sdk_config" != true ]]; then +if [[ -z "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" && ! -f "$ROOT_DIR/sonicflow_app/android-app/local.properties" ]]; then echo "==> android_build" echo "Skipping Android warning audit because no Android SDK path is configured." elif ! command -v java >/dev/null 2>&1; then echo "==> android_build" echo "Skipping Android warning audit because Java is not available on PATH." else + run_step android_unit_tests bash -lc "cd '$ROOT_DIR/sonicflow_app/android-app' && ./gradlew testDebugUnitTest" + check_step android_unit_tests + run_step android_build bash -lc "cd '$ROOT_DIR/sonicflow_app/android-app' && ./gradlew assembleDebug" check_step android_build fi