# AI-Driven Development Compatibility Report

**Project:** qslaravel (Laravel 13 / PHP 8.3+)
**Generated:** 2026-03-09
**Last Updated:** 2026-08-24
**Assessed by:** Claude Code (claude-opus-5)

---

## Executive Summary

| Metric | Value |
|--------|-------|
| **Overall AI Compatibility Score** | **79 / 100** |
| **Letter Grade** | **C+** |
| **Previous Score** | **74 / 100 (C+)** — assessed 2026-08-21 |
| **Risk Level for AI-Assisted Development** | **Low-Medium** |
| **Verdict** | **Two more god classes fell, and the static-analysis gate is finally green.** In 18 commits, `ChapterService` went **1,522 → 140 lines** and `MarketingV2Query` **1,418 → 496**, each becoming a facade over interface-backed collaborators — and unlike last period, **the long methods were split, not relocated**: methods over 100 lines dropped **39 → 28**, files over 1,100 lines **5 → 3**. Interfaces went **5 → 13** (all bound in `AppServiceProvider`), `declare(strict_types=1)` **11 → 28**. **`vendor/bin/phpstan analyse` now reports zero errors** — the level-5 gate is green for the first time since it was raised in July. A separate documentation sweep took **model method PHPDoc 13% → 100%**, middleware and Form Request class docblocks **10%/27% → 100%**, enums **0% → 100%**, and return types on models **63% → 100%** and middleware **45% → 100%**, lifting `app/`-wide return-type coverage **73% → 76%**. The suite is green at **2,578 passing** (8,870 assertions, 49s on 32 parallel processes). Three things hold the score below B. **CI exists but has never run** — `.github/workflows/ci.yml` is written, with all three gates wired and Pint correctly scoped to changed files, but the directory is **untracked**, so no push or PR has ever been checked. **`pint --test` still fails on 428 files** (302 in `app/`), though every one of the 16 newly extracted files is clean. And **`app/Http/Controllers/Api` is untouched at 6 return types across 72 methods (8%)**, with no Form Requests and untyped input via `getPostData()`. Unchanged: the **706-line** `getModuleAnalytics()` — now, by a wide margin, the worst artefact left — and **43** flat root-level services. |

---

## Category Scores

| # | Category | Previous | Current | Weight | Weighted | Status |
|---|----------|----------|---------|--------|----------|--------|
| 1 | Code Structure & Organization | 81 | 86/100 | 15% | 12.90 | `ChapterService` + `MarketingV2Query` decomposed; files >1,100 lines 5 → 3 |
| 2 | Consistency & Patterns | 74 | 78/100 | 15% | 11.70 | Interface+DI now spans 3 domains; all new code Pint-clean; 428 legacy files still red |
| 3 | Type Safety & Contracts | 65 | 70/100 | 20% | 14.00 | **PHPStan 4 → 0 errors (green)**; interfaces 5 → 13; `strict_types` 11 → 28; API controllers still 8% |
| 4 | Documentation & PHPDoc | 83 | 88/100 | 15% | 13.20 | Model method docs 13% → 100%; middleware/requests/enums class docs → 100% |
| 5 | Testing Infrastructure | 81 | 84/100 | 15% | 12.60 | +5 files, 2,578 pass; SQL-snapshot golden master added; CI authored but uncommitted |
| 6 | Method Complexity | 52 | 62/100 | 10% | 6.20 | >100-line methods 39 → 28 — **genuinely split this time**; `getModuleAnalytics()` still 706 |
| 7 | Navigability & Discoverability | 83 | 87/100 | 10% | 8.70 | 13 contracts across Auth/Chapter/Marketing; two 1,400-line files became readable facades |
| | **TOTAL** | **74** | | **100%** | **79.30** | **+5.30** |

### Score Visualization

```
Structure & Org     ███████████████████████████████████████████░░░░░░░  86%  (was 81%) ↑
Consistency         ███████████████████████████████████████░░░░░░░░░░░  78%  (was 74%) ↑
Type Safety         ███████████████████████████████████░░░░░░░░░░░░░░░  70%  (was 65%) ↑
Documentation       ████████████████████████████████████████████░░░░░░  88%  ← Strongest (was 83%) ↑
Testing             ██████████████████████████████████████████░░░░░░░░  84%  (was 81%) ↑
Method Complexity   ███████████████████████████████░░░░░░░░░░░░░░░░░░░  62%  ← Biggest gap (was 52%) ↑
Navigability        ███████████████████████████████████████████░░░░░░░  87%  (was 83%) ↑
```

**79 is one point below a B.** The point is buyable in roughly two days: commit and push the CI workflow that already exists, run `pint` once and commit the formatting diff, and add return types to the 66 untyped API controller methods. None of the three requires a design decision.

### Refactoring Impact (2026-08-24)

Eighteen commits over three days, in two disciplined arcs plus a documentation sweep.

**`ChapterService` is decomposed — and this time the methods were split too.** The class named "top structural target" in the last report is now a 140-line facade holding no logic:

| | 2026-08-21 | 2026-08-24 |
|---|---|---|
| `ChapterService` lines | **1,522** | **140** (−91%) |
| Constructor dependencies | many | **2** |
| Methods over 100 lines in the class | 3 (141 / 116 / 102) | **0** |

```
app/Services/Chapter/
├── ChapterProgressReader.php            748  (all read paths)
├── ActivityProgressUpdater.php          679  (all write paths, god-methods phase-split)
├── ActivityEventDispatcher.php          152  (event fan-out)
└── *Interface.php ×3                    159
app/Services/ChapterService.php          140  (was 1,522) — facade preserving the
                                              Api\ChapterController constructor contract
```

Characterization tests for the write paths (`ActivityProgressUpdaterTest`) were written **before** the extraction, and `ChapterProgressReaderTest` / `ActivityEventDispatcherTest` cover the rest.

**`MarketingV2Query` is decomposed.** 1,418 → **496**-line facade over five interface-backed query classes:

```
app/Http/Queries/Marketing/
├── EngagementProgressionQuery.php       691
├── SubscriptionBreakdownQuery.php       559
├── PaidSubscriberBreakdownQuery.php     443
├── SubscriptionFunnelQuery.php          297
├── AcquisitionTotalsQuery.php           169
└── *Interface.php ×5                    291
```

The refactor was protected by a **SQL-snapshot golden master** (`tests/Unit/Http/Queries/Marketing/MarketingV2QuerySqlSnapshotTest.php` + a committed `__sql__/marketingv2.php` fixture) that captures the generated SQL for every method under `DB::pretend()` and fails on any drift. That technique is the reusable output of this period: it makes an untested query class safe to restructure, and it applies unchanged to `UserQuery` (1,180) and `FirebaseNotificationQuery` (1,261).

**Long methods actually got shorter.** The last report's lesson — *"extracting a class is not the same as shortening its methods"* — was taken:

| | 2026-08-21 | 2026-08-24 |
|---|---|---|
| Methods > 100 lines | **39** | **28** (−11) |
| Files > 1,100 lines | **5** | **3** |
| Largest file in `app/` | `ChapterService` 1,522 | `UserProgramService` **1,410** |

All 11 methods that left the list were split into phase methods (7 in `MarketingV2Query`, 3 in `ChapterService`, plus one loop-body extraction). **No method entered the list.**

**PHPStan is green.** `vendor/bin/phpstan analyse --memory-limit=2G` reports **`[OK] No errors`** — down from 4 stale baseline entries and, before that, 16. The baseline shrank **2,896 → 2,785 lines** (478 → **464** grandfathered entries). Recommendation **0.1** is complete.

**A documentation and typing sweep landed across the layers the last report called out:**

| Layer | Metric | 2026-08-21 | 2026-08-24 |
|---|---|---|---|
| `app/Models` | method PHPDoc | 12/93 (**13%**) | 93/93 (**100%**) |
| `app/Models` | class PHPDoc | 91/94 (97%) | 94/94 (**100%**) |
| `app/Models` | return types | 59/93 (63%) | 93/93 (**100%**) |
| `app/Http/Middleware` | class PHPDoc | 1/10 (10%) | 10/10 (**100%**) |
| `app/Http/Middleware` | return types | 5/11 (45%) | 11/11 (**100%**) |
| `app/Http/Requests` | class PHPDoc | 18/66 (27%) | 66/66 (**100%**) |
| `app/Http/Requests` | method PHPDoc | 46/123 (37%) | 123/123 (**100%**) |
| `app/Enums` | class PHPDoc | 0/4 (0%) | 4/4 (**100%**) |
| **All of `app/`** | **return types** | **1,549/2,117 (73%)** | **1,708/2,236 (76%)** |

The `User` model docblock is the shape to copy: it explains *why* PII columns are absent (they moved to `tbl_UserInfo` on the `quitsureUsers` connection), why lookups hash rather than join, why several relations are declared `belongsTo` where `hasOne` would read better, and which four relations do not behave as their names suggest.

**Interfaces 5 → 13, `strict_types` 11 → 28.** Every extracted class this period ships with a contract, a `declare(strict_types=1)`, a class docblock, and a unit test. All 13 interfaces are bound in `AppServiceProvider::register()`.

**Regressed, unchanged, or still open:**

- **CI is written but has never run.** `.github/workflows/ci.yml` defines three jobs — `pest --parallel`, `phpstan analyse`, and `pint --test` scoped to the PR's changed files — with Composer caching and a generated `.env`. **`.github/` is untracked and absent from `origin/development`.** The single highest-leverage action in this report is now `git add .github && git push`.
- **`pint --test` still fails: 428 files** (was 436) — 302 in `app/`, 92 in `tests/`, 34 elsewhere; **1,918 individual fixer hits**. The top offenders are mechanical: `trailing_comma_in_multiline` (181), `ordered_imports` (180), `fully_qualified_strict_types` (171), `blank_line_before_statement` (147). **Every one of the 16 files added this period is clean**, so new code is already meeting the standard the CI job would enforce.
- **`app/Http/Controllers/Api` is completely untouched**: 6 return types across 72 methods (**8%**), 1 class docblock in 15 files, zero Form Requests, input via untyped `getPostData()`. This is now the only layer that improved on none of the three measured axes.
- **`ModuleAnalyticsController::getModuleAnalytics()` is still 706 lines** — unchanged for three periods, 93% of its 757-line controller, and 2.6× the next-largest method.
- **43 flat root-level services** — unchanged for three periods, despite the domain-namespace pattern now having ten examples.
- **Model factories hold at 9**, still `make()`-only, still adopted by only **13 of 193** test files.
- Loose comparisons **701 → 702**. Form Requests hold at **66**, inline validation at **6** call sites in `NewYearEmailController`.

---

## Codebase Overview

| Metric | Previous (2026-08-21) | Current (2026-08-24) |
|--------|----------|---------|
| Total PHP Files (app/) | 518 | **534** (+16) |
| Models | 94 | **94** |
| Controllers | 77 (15 API + 62 Web) | **77** (15 API + 62 Web) |
| Services | 118 | **124** (+6) |
| — of which flat at `app/Services/` root | 43 | **43** (unchanged) |
| — domain sub-namespaces | 9 | **10** (+`Chapter/`) |
| Query Classes | 93 | **103** (+10) |
| Test Files | 188 | **193** (+5) |
| Passing Tests | 2,522 pass / 10 skip / 0 fail | **2,578 pass / 10 skip / 0 fail** (8,870 assertions) |
| Suite runtime | 115s serial / 75s parallel (4 procs) | **181s serial / 49s parallel** (32 procs) |
| Form Requests | 66 | **66** (56 admin + 10 root) |
| **PHPStan** | **5 / 9 — 4 errors (baseline drift)** | **5 / 9 — 0 errors, GREEN** |
| PHPStan baseline | 2,896 lines / 478 entries | **2,785 lines / 464 entries** |
| **Pint (`--test`)** | **FAILS — 436 files** | **FAILS — 428 files** (302 `app/`, 92 `tests/`), 1,918 fixer hits |
| Middleware | 10 | **10** |
| Enums | 4 | **4** |
| Traits | 6 | **6** |
| Helpers | 6 | 6 |
| Console Commands | 14 | **14** |
| Model Factories | 9 | **9** (unchanged) |
| Models with `HasFactory` | 24 | **24** (8 have a factory) |
| `declare(strict_types=1)` files | 11 | **28** |
| **Interfaces / Contracts** | **5** | **13** |
| **CI/CD pipeline** | **None** | **Authored, uncommitted** — `.github/workflows/ci.yml`, 3 jobs, `.github/` untracked |
| Loose comparisons (`==` / `!=`) | 701 | **702** (+1) |
| **Methods > 100 lines** | **39** | **28** |
| **Files > 1,100 lines** | **5** | **3** |
| Return-type coverage (`app/`) | 73% | **76%** (1,708/2,236) |
| API routes (`routes/api.php`) | 78 | **77** |
| Third-Party Integrations | 10 | 10 |
| Database Connections | 4 | 4 |

### Measured Coverage Snapshot (2026-08-24)

| Layer | Class-level docblocks | Method-level docblocks | Return types (non-ctor) |
|-------|----------------------|------------------------|-------------------------|
| `app/Models` | **94/94 (100%)** ↑ | **93/93 (100%)** ↑↑ | **93/93 (100%)** ↑↑ |
| `app/Http/Queries` | 34/103 (33%) ↑ | **728/767 (95%)** | 420/761 (**55%**) ↑ |
| `app/Http/Controllers/Api` | 1/15 (7%) | **80/86 (93%)** | **6/72 (8%)** — unchanged |
| `app/Http/Controllers` (all) | 28/77 (36%) | **394/423 (93%)** | 227/355 (64%) |
| `app/Services` | 64/124 (**52%**) ↑ | 642/828 (78%) | **737/737 (100%)** |
| `app/Http/Requests` | **66/66 (100%)** ↑↑ | **123/123 (100%)** ↑↑ | **123/123 (100%)** |
| `app/Http/Middleware` | **10/10 (100%)** ↑↑ | **13/13 (100%)** ↑ | **11/11 (100%)** ↑↑ |
| `app/Enums` | **4/4 (100%)** ↑↑ | **11/11 (100%)** ↑ | **11/11 (100%)** |
| **All of `app/`** | **313/534 (59%)** | **2,093/2,418 (87%)** | **1,708/2,236 (76%)** |

Four layers are now complete on all three axes: **Models, Form Requests, Middleware, Enums**. The remaining holes are concentrated and named: **API controller return types (8%)**, **query-class return types (55%)** and **class docblocks** on controllers (36%), API controllers (7%) and services (52%).

### Service Organization (2026-08-24)

```
app/Services/
├── Marketing/      19 files
├── Firebase/       18 files
├── Cron/           13 files
├── Auth/           12 files  (Base, Email, Social, SSO + 4 extracted services + 4 interfaces)
├── User/            7 files
├── Chapter/         6 files  <- new this period (3 services + 3 interfaces)
├── Subscription/    2 files
├── Voucher/         2 files
├── Apple/           1 file
├── Google/          1 file
└── 43 root-level service files (still flat)
```

```
app/Http/Queries/
├── Marketing/      10 files  <- new this period (5 query classes + 5 interfaces)
└── 93 root-level query classes
```

---

## Category 1: Code Structure & Organization (86/100, was 81)

### Resolved This Period

**`ChapterService` is decomposed** — 1,522 → **140** lines, 2 constructor dependencies, three interface-backed collaborators. It was the largest file in `app/` and the report's top structural target; it is now a pure delegation layer that exists only to preserve `Api\ChapterController`'s constructor contract.

**`MarketingV2Query` is decomposed** — 1,418 → **496** lines over five interface-backed query classes under `app/Http/Queries/Marketing/`. This is the first time the query layer has received the treatment the service layer got in June.

**Files over 1,100 lines: 5 → 3.** Remaining: `UserProgramService` (1,410), `FirebaseNotificationQuery` (1,261), `UserQuery` (1,180).

**A tenth service namespace** (`Chapter/`) and the **first query sub-namespace** (`Queries/Marketing/`).

### What Still Works Well

**Domain decomposition is now the house style.** Five god classes have fallen to the same pattern — `FirebaseBusinessService` (228-line facade), `MarketingEmailService` (125), `CronService` (194), `BaseAuthService` (234), and now `ChapterService` (140) and `MarketingV2Query` (496). AI has six worked examples of the shape.

**Form Requests pervasive on the admin surface.** 56 classes under `app/Http/Requests/Admin/` across 25 controllers — and now 100% class- and method-documented.

**Registry pattern for dispatch.** `FirebaseCampaignRegistry`, `MarketingCampaignRegistry`, `CronJobRegistry`.

### What Hurts AI Comprehension

**43 services still flat at `app/Services/` root** — unchanged for three periods. Notification, commerce, integration, search, and core concerns remain mixed in one directory. This is the oldest untouched structural item in the report.

**93 query classes still directly under `app/Http/Queries/`** despite having nothing to do with HTTP. The new `Queries/Marketing/` sub-namespace shows the path.

**Two extracted classes are themselves large** — `ChapterProgressReader` (748) and `EngagementProgressionQuery` (691). Both are internally well-organized and single-purpose, but they are read-in-bulk files.

**`ModuleAnalyticsController` (757 lines) is 93% one method.**

---

## Category 2: Consistency & Patterns (78/100, was 74)

### New This Period

**Interface-backed constructor injection has become a convention.** It now spans three domains — `Auth/`, `Chapter/`, and `Queries/Marketing/` — with **13 contracts bound in `AppServiceProvider::register()`**. Last period this was "a seed, not yet a convention"; with three independent applications and a consistent shape (interface + concrete + binding + `strict_types` + unit test), it is now the pattern an AI should copy by default.

**New code is Pint-clean.** All 16 files added this period — 6 under `app/Services/Chapter/`, 10 under `app/Http/Queries/Marketing/` — pass `pint --test`, as do the 5 new test files. The formatting debt is entirely legacy.

**A characterization-test-first refactoring pattern is now demonstrated twice**: write the safety net (unit characterization tests for `ChapterService` write paths, a SQL golden master for `MarketingV2Query`), then extract.

### Still Red

**`vendor/bin/pint --test` fails on 428 of 799 files** — 302 in `app/`, 92 in `tests/`, 34 in `config/`, `database/`, `routes/`, `bootstrap/` — with **1,918 fixer hits**.

| Fixer | Hits |
|---|---|
| `trailing_comma_in_multiline` | 181 |
| `ordered_imports` | 180 |
| `fully_qualified_strict_types` | 171 |
| `blank_line_before_statement` | 147 |
| `not_operator_with_successor_space` | 140 |
| `class_attributes_separation` | 133 |
| `phpdoc_align` | 117 |
| `concat_space` | 108 |

The CI workflow handles this correctly — it runs Pint **only on the files a PR changes**, so legacy debt does not block merges while new code is held to the standard. That is the right call, and it means the whole-repo `pint` run is now a cleanup task rather than a prerequisite.

### Still Inconsistent

The **API surface remains bespoke and is now the sole untouched layer**. API controllers use neither Form Requests nor `validate()`: input arrives through `BaseApiController::getPostData()` and is checked ad hoc inside services. They carry **6 return types across 72 methods** and **1 class docblock across 15 files**. Every other layer improved this period; this one did not move at all. An AI adding an API endpoint still has no declarative input contract and no typed signature to copy.

**Inline validation holds at 6 call sites**, all `\Validator::make` in `NewYearEmailController` (lines 37, 124, 212, 298, 384, 470) — one file away from zero.

### Previously Resolved (retained)

- Form Request validation adopted admin-wide (3 → 55 → 66 classes). **DONE for admin** (2026-07-10).
- `FirebaseBusinessService` 40+ near-identical methods eliminated; stale-`microtime` worker bug fixed. **DONE** (2026-06-30).
- Registry + facade patterns standardized across Firebase, Marketing, Cron. **DONE** (2026-06-30).
- `userJournyLog()` → `userJourneyLog()` typo fixed (2026-03-13).
- `Platform::fromString()` replaces magic strings (2026-03-13).
- `validateUserIdMatch()` consolidates ~50 copy-pasted blocks (2026-03-13).
- `SourceDataService` constructor DI in `BaseAuthService` (2026-03-26).
- `CalculatesUtcOffset` trait extracts shared UTC logic (2026-03-26).

### Remaining Naming Inconsistencies

| Pattern | Example | Location |
|---------|---------|----------|
| PascalCase method | `LanguageList()` | `ProgramService.php` |
| PascalCase controller action | `GetUserProgramProgressByDayID()` | `Api/ChapterController.php` |
| Typo in public method | `upadateChapterStatus()` | `Api/ChapterController.php` |
| Lowercase compound | `activatevoucher()` | `VoucherService.php` |
| Numeric suffix | `getUsersList3()` | `UserQuery.php` |
| Mixed route naming | `SocialLogin` vs `getSubDetails` | `routes/api.php` |

Note that `ChapterService`'s facade **preserved** the PascalCase API (`GetUserProgramProgressByDayID`) to keep the controller contract intact — the right trade-off during a refactor, but it means the naming debt propagated into the new `Chapter/` namespace.

### Error Handling Patterns

| Layer | Pattern | Consistency |
|-------|---------|-------------|
| **Services** | 5 competing patterns (try-catch, inline, early return, rollback, none) | **Inconsistent** |
| **Controllers** | Mixed delegation and inline handling; `ApiResponse` trait used by **13 of 77** | **Inconsistent** |

---

## Category 3: Type Safety & Contracts (70/100, was 65)

### Key Metrics

| Metric | Previous | Current | Impact |
|--------|----------|---------|--------|
| **PHPStan level** | **5 / 9** | **5 / 9** | Held |
| **PHPStan run status** | 4 errors (baseline drift) | **0 errors — `[OK] No errors`** | **Gate is green** |
| PHPStan baseline | 2,896 lines / 478 entries | **2,785 / 464** | −14 grandfathered violations |
| **Interface/contract files** | **5** | **13** | Auth + Chapter + Marketing queries |
| Files with `declare(strict_types=1)` | 11 of 518 | **28 of 534** | Every new file |
| Loose comparisons (`==` / `!=`) | 701 | **702** | Flat |
| Return-type coverage (`app/`) | 1,549/2,117 (73%) | **1,708/2,236 (76%)** | Models & middleware to 100% |
| — `app/Models` | 59/93 (63%) | **93/93 (100%)** | Complete |
| — `app/Http/Middleware` | 5/11 (45%) | **11/11 (100%)** | Complete |
| — `app/Http/Queries` | 332/673 (49%) | **420/761 (55%)** | New Marketing classes are 100% |
| — **`app/Http/Controllers/Api`** | **6/72 (8%)** | **6/72 (8%)** | **Unchanged** |
| Typed DTO / value objects | `EmailResult`, `SendResult`, `CouponDecision`, `MarketingCampaignConfig`, `HourlyDateRange`, `DeepLinkPlatform` | same | Unchanged |
| Enum files | 4 | **4** | Now 100% documented |
| Form Request classes | 66 | **66** | Now 100% documented |

### What Improved

**The static-analysis gate is green.** `vendor/bin/phpstan analyse --memory-limit=2G --no-progress` → `[OK] No errors`. The four stale `ignore.unmatched` / `ignore.count` entries were cleared by regenerating the baseline, and the drift that had accumulated on `development` was fixed in `e5619c8f`. Recommendation **0.1** is complete. **The gate now works and passes — what is still missing is anything that runs it automatically.**

**Contracts nearly tripled: 5 → 13.** Auth (4), Chapter (3), Marketing queries (5), Voucher (1). Reading `ChapterProgressReaderInterface` (69 lines) tells an AI everything it needs about the read surface of what used to be a 1,522-line class.

**`strict_types` 11 → 28 files.** Runtime coercion is off across all of `Auth/`, `Voucher/`, `Chapter/`, and `Queries/Marketing/`.

**Models are fully typed and fully documented** — 93/93 return types, 93/93 method docblocks, 94/94 class docblocks. The data layer's shape is now readable without opening the database.

### What Still Needs Work

- **`app/Http/Controllers/Api` declares 6 return types across 72 methods (8%) — unchanged for three periods.** `CLAUDE.md` mandates a native return type on every method and cites `UserController` as a reference example; that file still has 0 of 6. With models, middleware, requests, and services all at 100%, this is the last large untyped surface and the cheapest remaining win.
- **`app/Http/Queries` is at 55%** (420/761) — 341 untyped query methods. The five new `Marketing/` classes are 100%; the 93 root-level classes drag the average.
- **`app/Http/Controllers` (web/admin) at 64%.**
- **`declare(strict_types=1)` in 28 of 534 files (5%).**
- **13 interfaces across 3 domains** — the other ~110 services remain concrete-only.
- Hungarian notation still obscures intent in DB columns (`iUserID`, `vEmail`, `bActive`) — though the `User` docblock now explains the scheme explicitly.
- **API input** is neither Form-Request-validated nor `validate()`-checked — it flows through `getPostData()` untyped.

---

## Category 4: Documentation & PHPDoc (88/100, was 83)

### Coverage by Layer (measured 2026-08-24)

| Layer | Class-level | Method-level | Change |
|-------|-------------|--------------|--------|
| Models | **94/94 (100%)** | **93/93 (100%)** | class 97% → 100%, method **13% → 100%** |
| Form Requests | **66/66 (100%)** | **123/123 (100%)** | class **27% → 100%**, method **37% → 100%** |
| Middleware | **10/10 (100%)** | **13/13 (100%)** | class **10% → 100%** |
| Enums | **4/4 (100%)** | **11/11 (100%)** | class **0% → 100%** |
| Query Classes | 34/103 (33%) | **728/767 (95%)** | class 25% → 33% (new Marketing classes) |
| Services | 64/124 (**52%**) | 642/828 (78%) | class 49% → 52% |
| Web/Admin Controllers | 28/77 (36%) | **394/423 (93%)** | unchanged |
| API Controllers | 1/15 (7%) | **80/86 (93%)** | unchanged |

### Why the Gain

A dedicated documentation sweep (`865ce1fd`) closed four layers completely, and every class added this period ships with a class docblock, parameter docs, and an array-shape `@return`.

**The `User` model docblock is now the strongest AI-facing documentation in the repo**, displacing `BaseFactory`. It explains the cross-database PII split (`tbl_UserInfo` on the `quitsureUsers` connection), why email lookups hash rather than join, why `iProgramID` and `iActiveDayID` are meaningless apart, that `SoftDeletes` is deliberately not used, that the declared `$createdAt` property is inert, and — critically — that **four relations do not behave as their names suggest**, each carrying its own warning. That is documentation that prevents a specific class of AI error, not documentation that restates the method name.

**Array-shape returns are now standard on the extracted code.** `ChapterService::GetUserProgramProgressByDayID()` documents `array{Status: bool, Data?: array, Message?: string, StatusCode?: int}` — an AI can construct a correct response without reading the implementation.

### What's Still Missing

- **Class-level docblocks on controllers (36%), API controllers (7%), and services (52%)** — the standing gap, and now the only significant documentation hole. `CLAUDE.md` requires one on every class.
- **Service method PHPDoc at 78%** — ~186 undocumented methods, concentrated in the extracted campaign/processor classes.
- **Query class docblocks at 33%** — 69 of 103 classes have no header.

### CLAUDE.md Provides Strong Project Context

`CLAUDE.md` remains a major asset. Its "every method declares a native return type" rule is now met by models, middleware, requests, enums, and services (100% each) but not by API controllers (8%) or queries (55%). Its "every class carries a PHPDoc block" rule is met by models, requests, middleware, and enums but not by controllers or services. The reference example it names, `Api/UserController`, still satisfies neither.

---

## Category 5: Testing Infrastructure (84/100, was 81)

### Test Distribution

| Category | Previous | Current | Notes |
|----------|----------|---------|-------|
| Total test files | 188 | **193** | +5 |
| Feature Tests | 89 | **89** | Unchanged |
| Unit Tests | 96 | **101** | +5 |
| Service unit tests | 80 | **83** | +3 (all `Chapter/`) |
| **Suite result** | 2,522 pass / 10 skip / 0 fail | **2,578 pass / 10 skip / 0 fail** | 8,870 assertions |
| Suite runtime | 115s serial / 75s parallel (4 procs) | **181s serial / 49s parallel** (32 procs) | Both exit 0 |
| Model Factories | 9 | **9** | Unchanged |
| Test files using `::factory()` | 13 | **13** | Adoption did not move |
| Static Analysis | PHPStan 5/9 — 4 errors | **PHPStan 5/9 — GREEN** | 0 errors |
| Code style | Pint FAILS — 436 files | **Pint FAILS — 428 files** | New code clean |
| **CI/CD Pipeline** | **None** | **Written, never run** | `.github/` is **untracked** |

### What Works

- **The suite is green and fast** — 2,578 passing, 0 failures, 49s on 32 parallel processes.
- **A SQL-snapshot golden master now exists.** `MarketingV2QuerySqlSnapshotTest` captures the SQL every `MarketingV2Query` method generates (via `DB::pretend()`) into a committed fixture and fails on drift. This is the technique that made a 1,418-line untested query class safe to decompose, and it transfers directly to `UserQuery` and `FirebaseNotificationQuery`.
- **Characterization tests preceded extraction** for `ChapterService`'s write paths — the tests were committed before the code moved.
- **`tests/Unit/Services/` mirrors the service tree**, including the new `Chapter/` namespace.
- **Modern Pest PHP** with BDD-style `describe()`/`it()` and AAA structure.

### What's Missing for AI

**CI exists on disk and nowhere else.** `.github/workflows/ci.yml` is complete and well-designed:

| Job | Command | Notes |
|---|---|---|
| Tests | `vendor/bin/pest --parallel` | PHP 8.3, Composer cache, generated `.env`, SQLite `:memory:` |
| Static analysis | `vendor/bin/phpstan analyse --memory-limit=2G --no-progress` | Boots the app for larastan (needs `APP_KEY`) |
| Code style | `vendor/bin/pint --test <changed files>` | Diffs against the PR base; skips when no PHP changed |

It runs on pushes to `development` and on every PR, cancels superseded runs, and correctly scopes Pint to changed files so legacy formatting does not block merges. **But `.github/` is untracked and absent from `origin/development`** — no push or PR has ever been checked by it. Everything the last three reports asked for has been built; it just has not been committed.

**Factories are static at 9, `make()`-only, and adopted by 13 of 193 test files.** `create()` still fails because the core `tbl_*` tables have no CREATE migrations, so there is no way to build a persisted fixture graph. No movement this period.

---

## Category 6: Method Complexity (62/100, was 52)

### God Classes — Status

| Class | Previous | Current | Status |
|-------|----------|---------|--------|
| `FirebaseBusinessService` | 2,619 lines | **228-line facade** | **Decomposed** ✅ |
| `MarketingEmailService` | large monolith | **125 lines** | **Decomposed** ✅ |
| `CronService` | monolith | **194 lines** | **Decomposed** ✅ |
| `BaseAuthService` | 1,465 lines, ~33 deps | **234 lines, 5 deps** | **Decomposed** ✅ |
| **`ChapterService`** | **1,522 lines** | **140-line facade, 2 deps** | **Decomposed** ✅ **(new)** |
| **`MarketingV2Query`** | **1,418 lines** | **496-line facade** | **Decomposed** ✅ **(new)** |
| `UserProgramService` | 1,410 | **1,410** | Unchanged — **now the largest file in `app/`** ⚠️ |
| `FirebaseNotificationQuery` | 1,261 | **1,261** | Unchanged ⚠️ |
| `UserQuery` | 1,180 | **1,180** | Unchanged ⚠️ |
| `CampaignQuery` | 895 | **895** | Unchanged — next tier |
| `UserRegistrationService` | 851 | **851** | Unchanged — from the auth split |
| `ChapterProgressReader` | — | **748** | New — extracted, read paths |
| `EngagementProgressionQuery` | — | **691** | New — extracted |
| `ActivityProgressUpdater` | — | **679** | New — extracted, write paths |
| `ModuleAnalyticsController` | 757 | **757** | Unchanged, 3 methods ⚠️ |

### God Methods — 28 methods exceed 100 lines (was 39)

| Lines | Method | Change |
|-------|--------|--------|
| **706** | `ModuleAnalyticsController::getModuleAnalytics()` | unchanged — **2.6× the next largest** |
| 266 | `ProgramService::getProgramSubscriptionDetails()` | unchanged |
| 248 | `RazorpayListenerQuery::updateRazorpayData()` | unchanged |
| 238 | `FunnelController::getFunnelResult()` | unchanged |
| 216 | `StripeListenerQuery::updateStripeData()` | unchanged |
| 178 | `UserFunnelController::getUserFunnelResult()` / `FormViewService::formview()` | unchanged |
| 169 | `RazorpayListenerQuery::enrollSubscriberFromListener()` | unchanged |
| 160 | `NotificationsWorkerCommand::handle()` | unchanged |
| 149 / 147 | `CouponService::activateCoupon()` / `VoucherService::activatevoucher()` | unchanged |
| 146 | `ApiTimingController::processPayloadTiming()` | unchanged |
| 142 | `MarketingV2Controller::campaignStyleExcel()` | unchanged |
| 141 | `AuthResponseService::buildUserData()` | unchanged |
| 132 | `AutoRenewFailedService::processUser()` | unchanged |
| 124 | `UserProgramService::updateLanguage()` | unchanged |
| 122 | `UserDayQuery::createNextUserDay()` | unchanged |
| 120 | `ProgramEnrollmentService::checkAndUpdateUserProgram()` | unchanged |
| — | ~~`ChapterService::updateChapterStatus()` (141)~~ | **split into phase methods** ✅ |
| — | ~~`ChapterService::updateExercise()` (116)~~ | **split** ✅ |
| — | ~~`ChapterService::updateAction()` (102)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::openedCompletedD1M1()` (123)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::getFreeSubscriptionType()` (121)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::completedD1M6()` (120)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::getUserPaidSubscribedProgramWise()` (116)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::getUserPaidSubscribedSubScreenWise()` (108)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::getProductLabelData()` (105)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::getAllData()` (102)~~ | **split** ✅ |
| — | ~~`MarketingV2Query::getUserSubscriptionData()` (101)~~ | **split** ✅ |

### What Improved

- **11 methods left the >100-line list and none entered it.** This is the difference from last period, when two long methods simply changed address. Both refactors phase-split their god methods as part of the extraction, exactly as recommended.
- **Files over 1,100 lines: 5 → 3.**
- **The largest file in `app/` is now 1,410 lines**, down from 1,522 — and every one of the top three is a known, named target.

### What Regressed or Held

- **`ModuleAnalyticsController::getModuleAnalytics()` (706 lines) is untouched for a third period.** It is now 2.6× the next-largest method and 93% of its controller. Nothing else in the codebase is close.
- The 17 surviving >100-line methods are spread thin — one or two per file — so they are individually cheap but collectively unglamorous work.

### AI Impact

Modifying chapter progress, a marketing metric, a Firebase campaign, or an auth sub-service is now low-risk: isolated, interface-backed, unit-tested classes with a facade preserving the public contract. Modifying `getModuleAnalytics()` (706) or `UserProgramService` (1,410) remains high-risk.

**Failure probability for non-trivial `ChapterService` modifications: ~50-60% → ~25-30%.**

---

## Category 7: Navigability & Discoverability (87/100, was 83)

### Strengths

| Asset | Value to AI |
|-------|------------|
| **13 interfaces across 3 domains** | Read a 44-69 line contract instead of a 1,400-line class |
| **Six facades over decomposed domains** | Firebase, Marketing email, Cron, Auth, Chapter, MarketingV2 all follow one shape |
| **100% model documentation** | Table, key, connection, and relation caveats readable without touching the DB |
| `CLAUDE.md` / `CLAUDE.local.md` | Excellent project + container context |
| `data-dictionary/` (121 files) | Auto-generated table/column docs for all 3 databases |
| **Domain-organized services (10 namespaces)** | Firebase/Marketing/Cron/Auth/Voucher/Chapter logic discoverable by path |
| **First query sub-namespace** (`Queries/Marketing/`) | Shows the path for the other 93 query classes |
| **Test tree mirrors service tree** | Find any service's test by path |
| `database/factories/` with documented `BaseFactory` | Test-data construction discoverable, with constraints stated up front |
| `.superpowers/sdd/` (3 decomposition plan sets, 44 files) + `docs/` (5 service guides + 7 plans + 2 specs) | Design rationale and task-by-task record for all three god-class splits |

### Weaknesses

| Issue | Impact |
|-------|--------|
| **43** flat root-level services | Notification/commerce/integration/search still ungrouped |
| 93 query classes still directly under `Http/Queries/` | Counter-intuitive location |
| API controllers carry no return types and no class docs | Signature intent invisible without reading the service |
| 4 database connections | Cross-database relationships unclear (though `User`'s docblock now explains the PII split) |
| Three files >1,100 lines | `UserProgramService` 1,410, `FirebaseNotificationQuery` 1,261, `UserQuery` 1,180 |

---

## AI Success/Failure Scenarios

### Scenario 1: "Add a new Firebase notification campaign"

| Factor | Assessment |
|--------|-----------|
| Finding the pattern | HIGH — 8 isolated campaign services to copy |
| Implementation | HIGH — small, single-purpose, registry-driven |
| Testing | HIGH — mirrored unit test per campaign exists |
| **Overall likelihood** | **80% correct on first attempt** (unchanged) |

### Scenario 2: "Add a new admin CRUD screen"

| Factor | Assessment |
|--------|-----------|
| Finding the pattern | HIGH — 56 `Http/Requests/Admin/**` classes across 25 controllers, now 100% documented |
| Controller + Form Request | HIGH — inline validation down to 6 sites, all in one file |
| Query layer | HIGH — 95% method-level PHPDoc (55% return types) |
| Test creation | HIGH — 89 feature tests to copy; `Program`/`Coupon`/`Voucher` factories exist |
| **Overall likelihood** | **85% correct on first attempt** (was 82%) |

### Scenario 3: "Add a new API endpoint for user preferences"

| Factor | Assessment |
|--------|-----------|
| Route registration | HIGH — clear pattern in `api.php` (77 routes) |
| Input validation | **LOW — API controllers use neither Form Requests nor `validate()`**; input flows untyped through `getPostData()` |
| Signature typing | **LOW — 6 return types across 72 API controller methods** |
| Service method | HIGH — services are 100% return-typed and 78% PHPDoc'd |
| Test creation | MEDIUM — feature tests exist to copy |
| **Overall likelihood** | **70% correct on first attempt** (unchanged — the API surface was again not touched) |

### Scenario 4: "Fix a bug in the auth / login flow"

| Factor | Assessment |
|--------|-----------|
| Finding relevant code | HIGH — `BaseAuthService` is 234 lines and delegates by name |
| Understanding the flow | HIGH — 5 deps, 4 interfaces, `strict_types` on |
| Making the fix | HIGH — typed params, PHPStan green |
| Verifying the fix | MEDIUM-HIGH — 5 dedicated unit test files |
| **Overall likelihood** | **72% correct on first attempt** (was 70% — PHPStan now green) |

### Scenario 5: "Change how a chapter or exercise is marked complete" — NEW

| Factor | Assessment |
|--------|-----------|
| Finding relevant code | **HIGH — `ChapterService` is 140 lines and names its collaborator per method** (was a 1,522-line class) |
| Understanding the flow | **HIGH — read/write split across 2 contracts; event fan-out isolated in a third** |
| Making the fix | HIGH — `strict_types`, array-shape `@return`, phase-split methods under 100 lines |
| Verifying the fix | HIGH — characterization tests written before the extraction |
| **Overall likelihood** | **78% correct on first attempt** (was ~35%) |

### Scenario 6: "Write a unit test for an existing service"

| Factor | Assessment |
|--------|-----------|
| Finding where the file goes | HIGH — `tests/Unit/Services/` mirrors `app/Services/` |
| Building test data | MEDIUM — 8 model factories + `BaseFactory`, `make()`-only, 86 models uncovered, adoption flat at 13/193 |
| Copying a pattern | HIGH — 83 service unit tests; the new `Chapter/` tests are the best current examples |
| Style compliance | MEDIUM — `tests/` still carries 92 Pint-failing files, but the 5 newest test files are clean |
| **Overall likelihood** | **75% correct on first attempt** (was 72%) |

---

## Actionable Recommendations

### Priority 1: High Impact, Low Effort — COMPLETED

#### 1.1 ~~Add return type hints to all service methods~~ DONE (2026-03-12) — verified 737/737 in `app/Services`
#### 1.2 ~~Add parameter type hints to all public methods~~ DONE (2026-03-12)
#### 1.3 ~~Create a `Platform` enum and replace magic strings~~ DONE (2026-03-13)
#### 1.4 ~~Extract controller validation to base method~~ DONE (2026-03-13)
#### 1.5 ~~Fix `userJournyLog` typo~~ DONE (2026-03-13)
#### 1.6 ~~Build V3 API layer with standardized patterns~~ DONE (2026-03-18), **REMOVED** (2026-03-24)
#### 1.7 ~~Add PHPDoc to all service methods~~ DONE (2026-03-24)
#### 1.8 ~~Fix service layer architectural violations~~ DONE (2026-03-26)
#### 1.9 ~~Decompose `FirebaseBusinessService` god class~~ DONE (2026-06-30)
#### 1.10 ~~Split Marketing & Cron services into domain sub-namespaces~~ DONE (2026-06-30)
#### 1.11 ~~Expand service unit test coverage (mirror service tree)~~ DONE (2026-06-30)

### Priority 0: Do These First (Score impact: +3-4 points, ~2 days total)

#### 0.1 ~~Get `vendor/bin/phpstan analyse` back to green~~ — **DONE (2026-08-24)**

`vendor/bin/phpstan analyse --memory-limit=2G` reports **`[OK] No errors`**. Baseline down to 2,785 lines / 464 entries.

#### 0.2 **Commit and push `.github/` — THE top priority, and it is now a one-line action**

**Current state:** `.github/workflows/ci.yml` exists, is complete and correct, and is **untracked** | **Effort:** 5 minutes
Everything the last three reports asked for has been written: three jobs (`pest --parallel`, `phpstan analyse`, `pint --test` on changed files), Composer caching, a generated `.env`, concurrency cancellation. It has never run because `.github/` is not in git and not on `origin/development`. `git add .github && git commit && git push` converts three advisory gates into enforced ones. Then mark the three jobs as required checks in branch protection.
**Sequencing is no longer a blocker**: the Pint job only checks files the PR touches, and all recently added code is Pint-clean, so the pipeline will be green on its first run without a repo-wide format first.

#### 0.3 Bring `vendor/bin/pint --test` to green repo-wide — downgraded to cleanup

**Current state:** **428 of 799 files fail** (302 in `app/`, 92 in `tests/`), 1,918 fixer hits | **Effort:** half a day
No longer a CI prerequisite (the workflow scopes Pint to changed files), so this is now debt reduction rather than a blocker. Run `vendor/bin/pint`, confirm the diff is whitespace/import/quote-style only, verify `vendor/bin/pest` still reports 2,578 passing, and commit it as a single formatting-only change. Doing it in one reviewable commit beats letting 428 files reformat piecemeal under feature work.

### Priority 2: High Impact, Medium Effort (Score impact: +5-7 points)

#### 2.1 Expand Form Request validation to the API surface — STILL OPEN, now the only untouched layer

**Current state:** 66 Form Requests across 35 controllers; **0 in `app/Http/Controllers/Api/`** | **Effort:** 1-2 days
Every other layer improved this period. The API surface uses neither Form Requests nor `validate()`; input flows untyped through `BaseApiController::getPostData()`. This is the largest consistency gap left in the codebase.

#### 2.2 Add return types to API controllers and query classes — **the cheapest win available**

**Current state:** `app/Http/Controllers/Api` **6/72 (8%)**; `app/Http/Queries` **420/761 (55%)**; `app/Http/Controllers` (web) 227/355 (64%) | **Effort:** 1 day
Models, middleware, Form Requests, enums, and services are all at 100% — this is what is left. `CLAUDE.md` mandates a native return type on every method and names `Api/UserController` as a reference example; that file declares zero across six methods. Most are one-token edits (`: JsonResponse`, `: array`, `: Collection`), and PHPStan level 5 — now green — will verify each one.

#### 2.3 Extend model factories and their adoption — STALLED

**Current state:** **9 factories**, unchanged; **13 of 193** test files use `::factory()`, unchanged | **Effort:** 1-2 days
Remaining priority models: `Chapter`, `Day`, `UserProfile`, `UserSubscription`, `UserDay`, `Exercise`. Two follow-ups matter as much as the count:
1. **Adoption** — the ratio did not move this period; the newest tests still hand-build fixtures.
2. **`make()`-only is a hard ceiling** — `create()` cannot work while `tbl_*` tables have no CREATE migrations. Adding test-only schema (or a `database/schema/*.sql` load step for the SQLite connection) would unlock persisted fixtures and is worth scoping separately.

#### 2.4 ~~Raise PHPStan level from 1 to 5~~ DONE (2026-07-18); gate green since 2026-08-24

**Current state:** Level **5/9** via larastan + a **2,785-line** baseline (464 grandfathered entries). Next step: work the baseline down, or raise to level 6 behind a fresh baseline.

#### 2.5 ~~Fix the suite's Mockery redeclaration error~~ DONE (2026-08-07)

#### 2.6 Add class-level docblocks — narrowed to three layers

**Current state:** models/requests/middleware/enums **100%** ✅; services **52%**, web controllers **36%**, API controllers **7%**, query classes **33%** | **Effort:** 1 day
Four layers were closed this period. What remains is controllers, services, and query classes. Use `app/Models/User.php` as the model — it documents constraints, cross-database behaviour, and the relations that lie about what they do.

#### 2.7 Standardize error handling with a response trait

**Current state:** 5 competing patterns; `ApiResponse` trait used by only 13 of 77 controllers | **Target:** single `FormatsApiResponse` trait | **Effort:** 2-3 days

### Priority 3: Medium Impact, Higher Effort (Score impact: +4-6 points)

#### 3.1 Decompose the remaining 1,100+ line files — `ChapterService` and `MarketingV2Query` DONE

```
UserProgramService          1,410  <- now the largest file in app/, TOP TARGET
FirebaseNotificationQuery   1,261
UserQuery                   1,180
CampaignQuery                 895
UserRegistrationService       851
ChapterProgressReader         748  <- new; well-structured but large
EngagementProgressionQuery    691  <- new; well-structured but large

~~ChapterService    1,522~~ -> 140 lines / 2 deps / 3 extracted services  ✅ DONE 2026-08-24
~~MarketingV2Query  1,418~~ -> 496 lines / 5 extracted query classes      ✅ DONE 2026-08-24
~~BaseAuthService   1,465~~ -> 234 lines / 5 deps / 4 extracted services  ✅ DONE 2026-08-21
```

**The pattern is proven three times over.** Write the safety net first — characterization tests for services, a **SQL-snapshot golden master** for query classes — then extract a cohesive responsibility, define an interface, bind it in `AppServiceProvider`, add `declare(strict_types=1)`, and **split the god methods into phase methods as part of the extraction**. That last step is what took the >100-line count from 39 to 28 this period after it barely moved (40 → 39) the period before.

`FirebaseNotificationQuery` and `UserQuery` are the natural next targets: both are query classes, and `MarketingV2QuerySqlSnapshotTest` is a directly reusable harness for them.

#### 3.2 Break up `ModuleAnalyticsController::getModuleAnalytics()` (706 lines) — now the standout

Unchanged for three periods. At 706 lines it is **2.6× the next-largest method** and 93% of its 757-line controller, and it is the last remaining artefact of its size anywhere in the codebase. The `ModuleAnalyticsQuery` + `ModuleAnalyticsRequest` extraction started this — finish it.

#### 3.3 Add `declare(strict_types=1)` to all files

**Current state:** **28 of 534** (was 11) | **Effort:** Automated via script + fix the type errors it surfaces
Four namespaces prove the pattern works here. Roll it out namespace by namespace, running `pest` after each.

#### 3.4 Group the 43 flat root-level services by domain

Unchanged for three periods — the oldest open structural item. The domain-namespace pattern now has ten examples.

```
app/Services/
├── Notification/   (Firebase facade, Postmark, SendGrid, Transactional)
├── Commerce/       (Coupon, Voucher, Billing)
├── Integration/    (Meta, Reddit, Gympass, Bajaj, Discourse)
├── Search/         (Search, Typesense*, SynonymSync)
└── Core/           (remaining)
```

#### 3.5 Move the 93 root-level query classes into domain sub-namespaces — **NEW**

`app/Http/Queries/Marketing/` is the first sub-namespace and it works. Apply the same grouping (User, Subscription, Payment, Notification, Analytics) to the remaining 93 flat classes, and consider moving the whole tree out of `Http/` while you are there.

### Priority 4: Advanced (Score impact: +3-5 points)

| Task | Effort | Score Impact |
|------|--------|-------------|
| Extend interfaces beyond Auth/Chapter/Marketing (13 exist, ~110 services concrete-only) | 3 days | +3 |
| Replace remaining 702 loose comparisons (`==` -> `===`) | 1-2 days | +2 |
| Add test-only schema so factories can `create()` | 1-2 days | +2 |
| Move Query classes from `Http/` to `app/Queries/` | 1 day | +1 |
| Raise PHPStan to level 6 behind a fresh baseline | 2 days | +2 |

---

## Implementation Roadmap

### Phase 1: Quick Wins — COMPLETED (2026-03-12 → 03-13)

| Task | Status | Score Impact |
|------|--------|-------------|
| ~~Add return/parameter types to all **service** files~~ | **DONE** (verified 737/737) | +8 |
| ~~Create `Platform` enum~~ | **DONE** | +1 |
| ~~Extract controller validation to base method~~ | **DONE** | +2 |
| ~~Fix `userJournyLog` typo~~ | **DONE** | +1 |

### Phase 1.5: PHPDoc Coverage — COMPLETED (2026-03-24 → 03-25)

| Task | Status |
|------|--------|
| ~~PHPDoc array shapes for V1 services~~ | **DONE** |
| ~~Model `@property` annotations~~ | **DONE** (100%) |
| ~~Query class method PHPDoc~~ | **DONE** (95%) |
| ~~Admin controller PHPDoc~~ | **DONE** (93%) |

### Phase 1.75: Architecture Cleanup — COMPLETED (2026-03-26)

| Task | Status | Score Impact |
|------|--------|-------------|
| ~~Extract CalculatesUtcOffset trait~~ | **DONE** | +1 |
| ~~Remove controller inheritance from services~~ | **DONE** | +1 |
| ~~Replace service locator with constructor DI~~ | **DONE** | +0.5 |
| ~~Optimize auth queries with column selection~~ | **DONE** | +0.5 |

### Phase 2: God-Class Decomposition & Testing — COMPLETED (2026-06-30)

| Task | Status | Score Impact |
|------|--------|-------------|
| ~~Decompose FirebaseBusinessService (2,619 → 228 facade)~~ | **DONE** | +3 |
| ~~Split Marketing into domain sub-namespace~~ | **DONE** | +1 |
| ~~Split Cron into domain services/processors~~ | **DONE** | +1 |
| ~~Expand tests 83 → 154 (mirror service tree)~~ | **DONE** | +2 |
| ~~Introduce Form Request validation (3 classes)~~ | **DONE (started)** | +1 |

### Phase 3: Standardization & Enforcement — Target 76 (74 → 79 delivered, exceeded)

| Task | Effort | Score Impact |
|------|--------|-------------|
| ~~Raise PHPStan to level 5~~ | **DONE (2026-07-18)** | **+2** |
| ~~Fix Mockery suite isolation~~ | **DONE (2026-08-07)** | **+1** |
| ~~Admin controller/query test + PHPDoc sweep~~ | **DONE (2026-08-07)** | **+1** |
| ~~Fix the 16 PHPStan errors~~ | **DONE (2026-08-21)** | **+2** |
| ~~Add 8+ model factories~~ | **DONE (2026-08-21)** | **+3** |
| ~~Regenerate the PHPStan baseline / reach 0 errors~~ | **DONE (2026-08-24)** | **+1** |
| ~~Model / middleware / request / enum PHPDoc + return types~~ | **DONE (2026-08-24)** | **+2** |
| ~~Author the CI workflow~~ | **DONE (2026-08-24)** | **+1** |
| **Commit and push `.github/` so CI actually runs** | **5 min** | **+1.5** |
| **Run `pint` and commit the formatting fix (428 files)** | **0.5 day** | **+1** |
| Add return types to API controllers + queries | 1 day | +2 |
| Expand Form Requests to the API surface | 2 days | +2 |
| Add class-level docblocks (services/controllers/queries) | 1 day | +1 |
| Standardize error handling with response trait | 2-3 days | +2 |

### Phase 4: Structural Depth — Target Score: 88

| Task | Effort | Score Impact |
|------|--------|-------------|
| ~~Decompose BaseAuthService (1,465 → 234)~~ | **DONE (2026-08-21)** | **+3** |
| ~~Decompose ChapterService (1,522 → 140)~~ | **DONE (2026-08-24)** | **+3** |
| ~~Decompose MarketingV2Query (1,418 → 496)~~ | **DONE (2026-08-24)** | **+2** |
| **Break up `getModuleAnalytics()` (706-line method)** | 1 day | **+2** |
| Decompose UserProgramService (1,410) | 3 days | +2 |
| Decompose UserQuery (1,180) / FirebaseNotificationQuery (1,261) via SQL golden master | 3 days | +2 |
| Split `UserRegistrationService` (851) further | 1 day | +1 |
| Add `declare(strict_types=1)` to all files (28/534 today) | 1 day | +2 |
| Group 43 flat root-level services by domain | 1 day | +1 |
| Group 93 flat query classes by domain | 1 day | +1 |
| Extend interfaces to core service dependencies | 3 days | +3 |

### Projected Score Progression

```
Mar 9:   ███████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░  45 (D)   <- Initial assessment
Mar 13:  ███████████████████████████░░░░░░░░░░░░░░░░░░░░░░░  53 (C-)  <- Phase 1 done
Mar 18:  ████████████████████████████████░░░░░░░░░░░░░░░░░░  63 (C)   <- V3 API done
Mar 24:  ██████████████████████████████░░░░░░░░░░░░░░░░░░░░  60 (C)   <- V3 removed, V1 PHPDoc
Mar 25:  ██████████████████████████████░░░░░░░░░░░░░░░░░░░░  61 (C)   <- Full PHPDoc all layers
Mar 26:  ███████████████████████████████░░░░░░░░░░░░░░░░░░░  62 (C)   <- Architecture cleanup
Jun 30:  █████████████████████████████████░░░░░░░░░░░░░░░░░  66 (C)   <- God-class decomposition + testing
Jul 10:  ██████████████████████████████████░░░░░░░░░░░░░░░░  68 (C)   <- Admin-wide Form Requests (3 -> 55)
Jul 18:  ███████████████████████████████████░░░░░░░░░░░░░░░  70 (C+)  <- PHPStan level 1 -> 5
Aug 7:   ███████████████████████████████████░░░░░░░░░░░░░░░  71 (C+)  <- +21 tests, suite green; offset by red PHPStan
Aug 21:  █████████████████████████████████████░░░░░░░░░░░░░  74 (C+)  <- BaseAuthService 1,465->234, first 5 interfaces,
                                                                        factories 1->9; offset by Pint 436 + RT correction
Aug 24:  ███████████████████████████████████████░░░░░░░░░░░  79 (C+)  <- ChapterService 1,522->140, MarketingV2Query
                                                                        1,418->496, PHPStan GREEN, interfaces 5->13,
                                                                        >100-line methods 39->28, models 100% documented
Phase 3: ██████████████████████████████████████████░░░░░░░░  84 (B)   <- CI running + pint green + API types + Form Requests
Phase 4: ████████████████████████████████████████████░░░░░░  88 (B+)
```

---

## Appendix A: Files Most Critical to Improve

| File | Lines | Why Critical | AI Difficulty |
|------|-------|-------------|---------------|
| `Http/Controllers/ModuleAnalyticsController.php` | 757 | Contains the **706-line** `getModuleAnalytics()` — largest method in the codebase by 2.6×, unchanged for three periods. **Top single target.** | HARD |
| `Services/User/UserProgramService.php` | **1,410** | **Now the largest file in `app/`** — core user-program logic; 124-line `updateLanguage()`, 112-line `updateProgram()`, 108-line `updateSubscription()` | HARD |
| `Http/Queries/FirebaseNotificationQuery.php` | 1,261 | Notification data layer; a direct candidate for the SQL-snapshot golden-master treatment | HARD |
| `Http/Queries/UserQuery.php` | 1,180 | Most-used query class; same golden-master approach applies | HARD |
| `Http/Queries/CampaignQuery.php` | 895 | Next tier of query-class debt | MEDIUM |
| `Services/Auth/UserRegistrationService.php` | 851 | Well-structured (13 phase methods) but the largest piece of the auth split | MEDIUM |
| `Services/Chapter/ChapterProgressReader.php` | 748 | New; interface-backed and tested, but a bulk-read file | MEDIUM |
| `Http/Queries/Marketing/EngagementProgressionQuery.php` | 691 | New; phase-split internally but large | MEDIUM |
| `Http/Controllers/NewYearEmailController.php` | 547 | Holds all 6 remaining inline `\Validator::make` call sites in the codebase | MEDIUM |
| `Services/ProgramService.php` | 504 | 266-line `getProgramSubscriptionDetails()` with queries in loops | MEDIUM |
| `Http/Controllers/Api/*.php` | — | **6 return types across 72 methods**; 1 class docblock in 15 files; no Form Requests; input via untyped `getPostData()`. **The only layer that improved on nothing this period.** | MEDIUM |
| `Services/ChapterService.php` | **140** | ~~Largest file in `app/`~~ — **now a thin, interface-backed facade** | **LOW** ✅ |
| `Http/Queries/MarketingV2Query.php` | **496** | ~~Largest query class~~ — **now a facade over 5 extracted query classes** | **LOW** ✅ |
| `Services/Auth/BaseAuthService.php` | 234 | ~~Core auth god class~~ — thin, interface-backed base | **LOW** ✅ |
| `Services/FirebaseBusinessService.php` | 228 | Facade — safe; depend on campaign services directly | LOW |

## Appendix B: Grading Scale

| Score | Grade | Meaning |
|-------|-------|---------|
| 90-100 | A | AI can work autonomously with high accuracy |
| 80-89 | B | AI produces correct code most of the time |
| 70-79 | C+ | AI needs moderate human review |
| 60-69 | C | AI frequently needs correction |
| 50-59 | C- | AI requires significant guidance |
| 40-49 | D | AI struggles without heavy supervision |
| 0-39 | F | AI cannot reliably work with this codebase |

---

## Appendix C: How These Numbers Were Measured (2026-08-24)

All figures are measured, not estimated. **Both columns of every comparison table come from the same scripts run over two trees** — the current working tree and `396cacae` (the commit carrying the 2026-08-21 report). Nothing is carried forward from the previous report's text.

```bash
# File / class counts (current tree)
find app -name "*.php" | wc -l                          # 534
find app/Services -name "*.php" | wc -l                 # 124
find app/Services -maxdepth 1 -name "*.php" | wc -l     # 43 (flat)
find app/Services -maxdepth 1 -type d | tail -n +2      # 10 domain namespaces
find app/Http/Queries -name "*.php" | wc -l             # 103
find tests -name "*.php" | wc -l                        # 193
find app/Http/Requests -name "*.php" | wc -l            # 66
find database/factories -name "*.php" | wc -l           # 9
grep -rl "declare(strict_types=1)" app/ | wc -l         # 28
grep -rl "^interface " app/ | wc -l                     # 13
grep -oE "[A-Za-z]+Interface::class" app/Providers/AppServiceProvider.php | sort -u | wc -l   # 13 bound

# Quality gates
XDEBUG_MODE=off vendor/bin/phpstan analyse --memory-limit=2G --no-progress
#   -> [OK] No errors
XDEBUG_MODE=off vendor/bin/pest --parallel
#   -> 2,578 passed / 10 skipped / 0 failed, 8,870 assertions, 49.20s, 32 processes
XDEBUG_MODE=off php -d memory_limit=1G vendor/bin/pest
#   -> same result serially: 2,578 passed / 10 skipped / 0 failed, 181.36s
#      (the default 128M CLI limit exhausts partway on this host; raise it or run --parallel)
XDEBUG_MODE=off vendor/bin/pint --test
#   -> FAIL: 428 of 799 files, 1,918 fixer hits (app 302, tests 92, other 34)

# Loose comparisons (identical regex against both trees)
grep -rEo '[^=!<>]==[^=]|!=[^=]' app/ --include="*.php" | wc -l   # 702 (was 701)

# CI status
git ls-files .github            # empty -> untracked
git ls-tree origin/development  # no .github entry

# Historical baseline
git archive 396cacae | tar -x -C /tmp/base0821   # re-measure the 2026-08-21 tree
```

**Docblock, return-type, long-method, and file-size figures** come from a single `token_get_all()`-based PHP script run over both trees. Methodology notes:

1. **Return types exclude constructors**, which cannot declare one in PHP.
2. **Method length** is measured from the `function` keyword's line to the line of its closing brace. Named functions only — closures and arrow functions are excluded.
3. **A docblock counts for a class or method** when it ends within 6 lines of the declaration, which tolerates intervening attributes and modifiers.
4. This script reproduces the 2026-08-21 report's published figures exactly for class docblocks, return types, method counts (39 >100-line methods), and file sizes. **Method-level docblock counts differ by 1-4 points** on some layers (e.g. it measures `app/Services` at 77% where the last report printed 73%), because the previous period used a slightly stricter adjacency rule. Both columns here use the current rule, so the deltas are internally consistent.
5. **Pint counts are files with at least one violation**, matching the previous report's methodology; the 1,918 figure counts individual fixer applications and is new this period.

---

*Report generated by Claude Code. Last updated 2026-08-24 after an 18-commit period that decomposed two more god classes and turned the static-analysis gate green. Score moved 74 → 79 (C+). The headline: `ChapterService` fell **1,522 → 140** and `MarketingV2Query` **1,418 → 496**, each into interface-backed collaborators behind a facade — and this time the god methods were split as part of the work, taking methods over 100 lines from **39 → 28** and files over 1,100 lines from **5 → 3** with nothing entering either list. `vendor/bin/phpstan analyse` now reports **zero errors**. Interfaces went 5 → 13, `strict_types` 11 → 28, and a documentation sweep took models to **100% on class docs, method docs, and return types**, with middleware, Form Requests, and enums joining them — lifting `app/`-wide return-type coverage to 76%. The suite is green at 2,578 passing. What keeps this at 79 rather than a B is three specific, non-architectural things. **CI is written and has never run**: `.github/workflows/ci.yml` wires all three gates correctly and scopes Pint to changed files, but `.github/` is untracked and absent from the remote — committing it is a five-minute action and the single highest-leverage one in this report. **`pint --test` still fails on 428 files**, though every file added this period is clean, so this is now cleanup rather than a blocker. And **`app/Http/Controllers/Api` improved on nothing**: 6 return types across 72 methods, one class docblock in fifteen files, no Form Requests, untyped input through `getPostData()`. After those: `getModuleAnalytics()` at **706 lines** — untouched for three periods and now 2.6× the next-largest method in the codebase — and `UserProgramService` (1,410), which inherits the crown `ChapterService` just gave up.*
