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
2 changes: 1 addition & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CodeRabbit configuration for boxlore (Android / Kotlin / Jetpack Compose).
# Docs: https://docs.coderabbit.ai/reference/configuration
# Aligns with AGENTS.md, ARCHITECTURE.md, and .cursor/rules/*.mdc
# Aligns with AGENTS.md, ARCHITECTURE.md, and docs/TESTING.md.

language: en-US

Expand Down
10 changes: 4 additions & 6 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,23 @@ Do **not** use sentence-case titles without a type prefix (e.g. avoid `Polish th

## Merge gate (required before merge)

Unit tests, detekt, ktlint, and the Kover coverage gate run on **every PR push** (a new commit cancels the previous in-progress unit run; plus optional Actions → Run workflow). There is **no merge queue**.
The unit workflow runs architecture checks, detekt, JVM tests, Kover coverage, dependency guards, Python release-tooling tests, and Android lint on PR pushes unless a safe docs/chore PR uses `[skip unit]`. A new commit cancels the previous in-progress run; Actions → Run workflow runs the full suite. Run `./gradlew ktlintCheck` locally for Kotlin changes; ktlint is not part of the current PR workflow. There is **no merge queue**.

Master is protected by a branch ruleset. Required checks before merge:

1. **`testDebugUnitTest`** — PR pushes (new commits cancel the prior run; `[skip unit]` in the title no-ops for safe docs/chore only)
2. **`coderabbit-threads-resolved`** — every non-outdated CodeRabbit review thread is marked Resolved

Also on PRs (not ruleset-required): SonarCloud App, CodeRabbit App, Gitleaks.
Also on PRs (not ruleset-required): SonarCloud App, CodeRabbit App, Gitleaks. The PR must have **zero Sonar new-code issues**.

Flow:

1. Open the PR and iterate (unit suite cancels prior runs).
2. Address **every** CodeRabbit finding and mark every CodeRabbit thread **Resolved**; wait for unit + **`coderabbit-threads-resolved`**.
3. If review decision is **`CHANGES_REQUESTED`**, do not agent-merge — ask a human to merge (or dismiss) manually.
2. Address **every** CodeRabbit finding and mark every CodeRabbit thread **Resolved**; fix Sonar new-code issues and wait for unit + **`coderabbit-threads-resolved`**. The bare CodeRabbit status only confirms that its review completed.
3. If review decision is **`CHANGES_REQUESTED`**, stop automated merging. Do not dismiss the review or force-merge; ask a maintainer to merge (or dismiss) manually.
4. Otherwise squash-merge when required checks are green.
5. Optional: Actions → Run workflow (`Unit Tests`) for a manual full gate.

Scheduled bots push to `master` via the **boxlore-master-pusher** GitHub App (ruleset Integration bypass).

## Summary

<!-- What changed and why. Be specific. Exact CHANGELOG / README wording goes in Release copy below. -->
Expand Down
16 changes: 9 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,14 @@ bench_node.js
*.log
*_logcat.txt

# Standalone Proxy — lives in github.com/ashwkun/Proxy
# Local standalone backend checkout
proxy/

# Sub-projects (local only)
admin-panel/
telemetry-worker/

# Hosting lives in aswin.cx / ashwkun/aswin-cx — ignore any leftover local docs/web.
# Only the web site — not the rest of docs/.
# Local website checkout; keep the rest of docs/ tracked.
docs/web/

# Transformers model cache
Expand Down Expand Up @@ -75,8 +74,11 @@ posthog-setup-report.md
# Editor / agent settings
.vscode/
.claude/
# Local agent/editor noise (AGENTS.md + .cursor/rules are committed)
# Local agent/editor cache
.agents/**/cache/
.cursor/debug-*.log
.cursor/plans/
.cursor/settings.json
/.cursor/

# Maintainer-only instructions and Codex hooks
/AGENTS.internal.md
/.codex/hooks.json
/.codex/hooks/
4 changes: 4 additions & 0 deletions .worktreeinclude
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy private guidance into local Codex-managed worktrees; keep it out of Git.
AGENTS.internal.md
.codex/hooks.json
.codex/hooks/
140 changes: 33 additions & 107 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The graph is layered so playback and features depend inward on catalog and lower

### Decoupling and module granularity

When a domain, presentation flow, or capability is significant enough to form a cohesive domain (such as the settings hub, listening history timeline, or authentication SDK), **keep it decoupled in its own standalone module**. Avoid co-locating unrelated subsystems inside general-purpose modules (e.g., settings inside `:feature:home`, history inside `:feature:library`, or auth credentials inside `:core:network`). If something is significant enough and makes architectural sense to decouple, keep it separate. Any module extractions or architectural decoupling must strictly be executed **only post explicit confirmation and approval from the user**.
Keep cohesive domains and flows in focused modules: settings belongs in `:feature:settings`, listening history in `:feature:history`, and authentication in `:core:auth`. Avoid placing unrelated capabilities in general-purpose modules. Obtain explicit maintainer approval before extracting modules or changing architectural boundaries.

## Identity and storage

Expand Down
47 changes: 47 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,50 @@ The recommendation, search, and catalog backend proxy is tracked in a separate p
## Local Development & Personal Use

In accordance with the [PolyForm Strict License 1.0.0](LICENSE), you are welcome to build and run boxlore locally for personal study, testing, and private noncommercial use. Refer to the [README.md](README.md) and [ARCHITECTURE.md](ARCHITECTURE.md) for build setup and module details.

### Requirements

| Tool | Requirement |
| :--- | :--- |
| Java | JDK 17; set `JAVA_HOME` and the Android Studio Gradle JDK to this installation |
| Android SDK | Platform 36, Platform-Tools, and the Build-Tools version requested by the Android Gradle plugin |
| Gradle | Use the checked-in `./gradlew` wrapper; no separate Gradle installation is needed |
| Node.js | Version 20 and npm for scripts; not needed for ordinary Android builds |
| Python | Python 3 for local configuration and release-tooling tests |
| Device | Android 12 / API 31 or newer when running the app |

Set `ANDROID_HOME` to the SDK installation and add its `platform-tools` directory to `PATH`. Accept SDK licenses through Android Studio or `sdkmanager --licenses`. Verify that `java -version` reports 17 before building.

### Build configuration

From the repository root, create non-secret local configuration:

```bash
bash scripts/ci/write-cloud-agent-local-config.sh
./gradlew assembleDebug
```

The configuration script writes `sdk.dir` in `local.properties` and a non-secret `app/google-services.json` stub. It preserves either file when it already exists. Verify `sdk.dir` if the script cannot find your SDK. Both files are ignored by Git; keep them untracked and never commit real service configuration, keys, or signing material.

The stub supports building and JVM checks without production credentials. It does not provide working Firebase services or API access. The APK is written to `app/build/outputs/apk/debug/app-debug.apk`.

### Running locally

Building and launching have different configuration requirements. The app constructs its API client during startup, so an empty `BOXLORE_API_BASE_URL` causes startup to fail. For a local launch without backend access, add these non-secret placeholders to the existing `local.properties`, retaining `sdk.dir`:

```properties
BOXLORE_API_BASE_URL=https://api.boxlore.example/
BOXLORE_PUBLIC_KEY=demo-placeholder-key
```

Rebuild after changing these values. The placeholder has a valid URL format but supplies no backend: search, recommendations, and briefing cannot retrieve live results. Use local library, downloads, and RSS/OPML flows for checks that do not need the API. Working backend and Firebase services require separately provisioned configuration.

Connect a device or start an emulator, then install and launch:

```bash
adb devices
./gradlew installDebug
adb shell am start -n cx.aswin.boxlore/.MainActivity
```

Use the [test selection guide](docs/TESTING.md#choose-checks-for-a-change) for validation commands and [architecture guide](ARCHITECTURE.md) for module ownership. Local maintainer environments also use root `AGENTS.internal.md` for private workflow and operational instructions.
34 changes: 28 additions & 6 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ How Boxlore is tested: layers, commands, coverage floors, architecture gates, an

Automated coverage focused on **hermetic JVM** for product logic (queue fill, ranking, catalog, prefs, feature `logic/`). High Kover floors fail CI on drop. Architecture guards fail the unit PR job on graph drift.

**Strategy:** constructors, domain ports, shared fakes in `:core:testing`, assemblers, Turbine. No MockK/Hilt. No Application-backed Home/Info suites. Media3 service / `PlaybackRepository` stay out of the line gate; covered by policy unit tests. Maestro YAML is validated nightly (no paid Maestro Cloud device runs).
**Strategy:** constructors, domain ports, shared fakes in `:core:testing`, assemblers, Turbine. No MockK/Hilt. No Application-backed Home/Info suites. Media3 service / `PlaybackRepository` stay out of the line gate; covered by policy unit tests. Maestro YAML is validated nightly; the existing optional Cloud job runs only when its credentials are configured and is not a PR gate.

## Layers

Expand All @@ -27,11 +27,30 @@ Automated coverage focused on **hermetic JVM** for product logic (queue fill, ra
| Android lint | `./gradlew lintDebug` | Manifest / resource / API lint | Done |
| Coverage (Kover) | `./gradlew :koverVerifyMerged` | Merged floor (ratchet toward 80%) | WIP |
| Screenshots | `screenshots/baselines/` + optional Roborazzi (local) | Visual regressions | Optional |

| Maestro | `maestro/` YAML validate | Flow file presence/syntax | Done |

Architecture boundaries: [`ARCHITECTURE.md`](../ARCHITECTURE.md).

## Choose checks for a change

Run commands from the repository root. Start with the affected behavior and broaden to shared consumers when a change crosses module boundaries. See [local setup](../CONTRIBUTING.md#local-development--personal-use) for tool and configuration requirements.

| Change | Checks |
| :--- | :--- |
| Android logic or state | Add or extend JVM regression coverage; run `./gradlew :<module>:testDebugUnitTest` for affected modules (for example, `:core:playback:testDebugUnitTest`), then `./gradlew testDebugUnitTest --continue` for shared behavior |
| Module boundaries, dependencies, or composition | Run both `scripts/ci/check-feature-no-boxlore-database.sh` and `scripts/ci/check-feature-no-posthog.sh` with Bash; run `./gradlew :core:testing:testDebugUnitTest :app:dependencyGuard :core:catalog:dependencyGuard :core:playback:dependencyGuard` |
| Kotlin, Android resources, or manifests | Run `./gradlew detekt ktlintCheck lintDebug`; include affected JVM tests for changed behavior |
| App UI or runtime behavior | Build/install with `./gradlew installDebug` on an available device; verify the affected screen and action; use optional local Roborazzi when appropriate |
| Catalog sync logic | Run `npm ci --prefix scripts` once, then `npm run test:sync --prefix scripts`; deployment is a separate operation requiring the internal runbook |
| New-episode notification checker | Run `npm ci --prefix scripts` once, then `npm run test:check-new-episodes --prefix scripts` |
| Tracked-feed repair | Run `npm ci --prefix scripts` once, then `node --test scripts/backfill-tracked-podcast-feeds-lib.test.js` |
| Changelog or release tooling | Run `python3 -m unittest discover -s .github/scripts -p 'test_*.py' -v` |
| Documentation only | Review local links and instruction destinations; run `git diff --check` |

For Android code changes, the full CI command is `./gradlew detekt testDebugUnitTest :koverVerifyMerged :app:dependencyGuard :core:catalog:dependencyGuard :core:playback:dependencyGuard --continue`, with `./gradlew lintDebug` in a separate job. The unit workflow also runs the architecture shell guards and Python release-tooling tests. `ktlintCheck` is a local check; it is not part of the current PR workflow.

Report which checks completed and distinguish automated coverage from manual checks or paths that still need verification. Script tests exercise local logic; they do not verify a production deployment.

## Stack

- JUnit 5 (+ Vintage where leftovers remain)
Expand Down Expand Up @@ -119,6 +138,7 @@ bash scripts/ci/check-feature-no-posthog.sh

Detekt: `config/detekt/{detekt.yml,baseline.xml}`.
ktlint: per-project baselines under `config/ktlint/`.
Use the root `detekt` task; individual modules do not all define one. These commands do not rewrite source files.

## Module × layer checklist

Expand Down Expand Up @@ -152,7 +172,7 @@ Application-backed Home/Info suites are **not** pursued; hermetic `logic/` + ass
| :--- | :--- |
| Flow YAML under `maestro/` | Done |
| Nightly YAML validate | Done |
| Maestro Cloud device runs | Out of scope (not subscribed) |
| Maestro Cloud device runs | Optional; requires configured service credentials |

See [`maestro/README.md`](../maestro/README.md).

Expand All @@ -170,12 +190,14 @@ See [`docs/screenshots/README.md`](screenshots/README.md).

| Workflow | Runs | When | Status |
| :--- | :--- | :--- | :--- |
| `unit-tests.yml` | Architecture + detekt + unit + Kover + lint + Dependency Guard | PR / dispatch | Done |
| `unit-tests.yml` | Architecture + detekt + unit + Kover + lint + Dependency Guard + Python release tests | PR / master push / dispatch | Done |
| `coderabbit-threads-resolved.yml` | Fail unless all non-outdated CodeRabbit review threads are Resolved | PR / review | Done |
| `gitleaks.yml` | Secret scan | PR / push to master | Done |
| `maestro-nightly.yml` | Validate Maestro YAML | Nightly / manual | Done |
| `maestro-nightly.yml` | Validate Maestro YAML; optional Cloud device job when configured | Nightly / manual | Done |

**Merge gate:** master uses a branch ruleset with no merge queue. Required checks are **`testDebugUnitTest`** and **`coderabbit-threads-resolved`**. SonarCloud, CodeRabbit, and Gitleaks also run on PRs. Fix all Sonar new-code issues and address every CodeRabbit finding, marking every review thread Resolved; the bare CodeRabbit status only confirms that the review completed.

**Merge gate:** master uses a branch ruleset (no merge queue). Required checks: **`testDebugUnitTest`** and **`coderabbit-threads-resolved`**. SonarCloud / CodeRabbit / Gitleaks still run on PRs (fix Sonar new-code issues; resolve CodeRabbit threads — the bare `CodeRabbit` status only means the review finished). The unit suite cancels prior in-progress runs on each PR push (or via Actions → Run workflow). Put `[skip unit]` in the PR title to no-op that job for docs/chore-only changes (still reports green; `workflow_dispatch` always runs full). Bots push to master via **boxlore-master-pusher** (ruleset Integration bypass).
The unit suite cancels prior in-progress runs on each PR push. `[skip unit]` in the PR title no-ops that job only for docs/chore changes with no logic risk; it still reports green. Actions → Run workflow always runs the full suite. If the review decision is `CHANGES_REQUESTED`, stop automated merging and have a maintainer handle the review or merge manually. Otherwise squash-merge only after the required checks are green and review requirements are satisfied. Repository-administration procedures belong in root `AGENTS.internal.md`.

Protected inputs: `app/google-services.json` is gitignored; CI writes a non-secret stub.

Expand Down
Loading
Loading