tey/mod
A lightweight Laravel/PHP package for building modular applications: organize code into self-contained modules, register providers, and keep large projects maintainable with a simple, configurable structure. Ideal for teams splitting features into reusable components.
Architecture fit. tey/mod remains a developer-time placement and generation toolkit, not a runtime library. It hooks into Artisan generators so that mod:model, mod:controller, mod:dto and ~30 sibling commands write to wherever the configured layout says, instead of Laravel's fixed app/ conventions. The architecture is deliberately thin: layouts are declarative (config/mod.php plus Mod::layout(...) calls in a service provider), file types are registered with ->generates(), group folders with ->path(), nesting with ->allowsNesting(). There is no opinionated scaffold shipped and no business-logic pattern imposed — it reuses Laravel's own stubs and conventions. For a TPM the framing is unchanged: the package is an architecture codifier, and its value is that "where does this class go" stops being a code-review argument and becomes configuration plus a generator default. v0.7.2 does not alter that architecture — it strengthens the preview surface, which is the part of the design we were already leaning on.
Integration feasibility. High, with a caveat, and v0.7.2 materially improves the middle of that story. The default layout is still laravel — identical output to make:* — so the zero-risk adoption wedge holds: require-dev, change nothing, run mod:list --json and commit a baseline. The release's headline addition is that scaffold dry runs now report existing whole-file members as unchanged or edited by byte-comparing against what the current recipe would write, exposed as an optional state field in JSON and as a label in human output. That converts the dry-run from "where would files go" into "what would actually change," which is the difference between a soft guardrail and a CI-enforceable one. The remaining caveat is unchanged: any layout with roots outside app/ (ddd maps Domain\ to src/Domain/) requires php artisan mod:autoload, which mutates composer.json and shells out to Composer. That is still the only place this package writes outside generated source files, and still needs a deliberate, reviewed pipeline step.
Technical risk. The dominant risk is still maturity, not design: 0.x versioning, a single maintainer (Jasper Tey / Teylabs), and a first-release-date metadata field of 2026-10-13 that should be verified against Packagist before it appears in a dependency audit. The engineering signals remain strong — ~2,000 Pest tests, PHPStan across src and tests, Pint, a Linux+Windows CI matrix on PHP 8.3–8.5 and Laravel 12–13, plus a published-API snapshot and audits that contributors are told never to weaken. v0.7.2 is a good signal on cadence and on responsiveness to correctness bugs: #164 fixes tree scaffolds ignoring --force/--skip-existing and mispredicting existing-file outcomes between human and JSON dry runs, and #165 fixes parts/inserts losing a root migration's resolved identity and timestamp. Both were latent correctness traps in exactly the workflow we intended to gate on.
Two new risks introduced or made visible by this release. First, a behavioural change in patch scope: dry runs no longer repeat placeholder warnings for class members because each member is now rendered by its own generator. Anything parsing human-readable dry-run output will see different text on a 0.7.1 → 0.7.2 bump; parse --json only. Second, the changelog shows two new automated-review workflows merged (#161 comment-triggered OpenCode review, #163 review via the free LongCat model). That is an upstream supply-chain question for us, not a defect: an org evaluating this package should know whether PR diffs transit a third-party free model endpoint and whether that is acceptable under its own procurement and IP rules. It does not affect our runtime — the package is a dev dependency with no runtime surface — but it belongs in the vendor questionnaire.
Key questions.
tests/Fixtures/published-api.json) intended as a semver contract we can rely on for custom layouts — and does it cover the new state field in scaffold dry-run JSON?@api and @internal, and will our custom layouts only ever touch @api?mod:autoload handle a composer.json that already contains hand-written PSR-4 mappings, or do conflicts have to be resolved manually every time?src/Domain tree?Stack fit. Laravel 12/13 on PHP 8.3–8.5, any frontend — the docs explicitly say to inspect page_name and frontend paths in mod:list --json rather than assume an Inertia stack, and ->frontend(pages:, components:, css:, views:) is opt-in. The agent-facing surface (AGENTS.md, skills/mod-development/SKILL.md, resources/boost/) remains the differentiator and is now sharper: agents get a deterministic contract — run mod:list --json, preview with --dry-run --json, read warnings, would_write and the new state, then generate. The unchanged/edited distinction is directly useful for agent loops, because it lets an agent distinguish "this scaffold is already correct" from "this scaffold exists but diverges" without re-reading files.
Migration path. Four gated phases, unchanged in shape:
require-dev tey/mod, keep the laravel layout, generate nothing. Run mod:list --json and commit the output as the baseline inventory.modules or features layout for it only. Measure review friction and agent retry rate on that domain, and now also measure scaffold re-run noise (state: unchanged rate) as a signal that the recipe is stable.ddd only if there is genuine domain complexity demanding it; ddd is the highest-cost layout (roots outside app/, controllers/requests living separately from domain code, mod:autoload required). Do not adopt it "for symmetry".Mod::layout('areas')->extends('modules') with ->path('src/Areas/{area}') and ->generates(...) is the escape hatch. extends() must come first and copies the parent at that moment, so pin it and review it like any architecture decision.Compatibility. Coexists with native make:* — conflicts are generation-time only, and the package refuses --path/--realpath on mod:migration, a deliberate guardrail. The shared mutable state is the Composer PSR-4 map; gate it. Pin at 0.7.2 or tighter for the pilot: 0.x patches have shipped behavioural changes, and the fixes in this release are only valuable if the whole team and all agents run the same build.
Sequencing and gates. Per PR: composer test, composer analyse, composer lint. In our CI, additionally: mod:autoload --dry-run (fails if composer.json would change), and mod:list --json diffed against the committed baseline (fails on unintended layout drift). Add composer format, and set MOD_FRONTEND_BUILD=1 only if we adopt frontend paths. Agents must be instructed to use --dry-run --json before every real generation and to treat would_write: false as a blocker, not a warning. With v0.7.2, extend the scaffold gate: capture state in the JSON and fail if a re-run of an existing scaffold reports edited where unchanged was expected — that is now a cheap, deterministic drift detector for generated code. Do not parse human dry-run text in CI; this release changed it.
Maintenance. Low and reversible. No runtime footprint, no migrations, no cache driver, no queue worker change. The maintenance burden shifts from code to config: config/mod.php and the service-provider layout declarations become load-bearing architecture and should carry CODEOWNERS. Upgrades are a composer update on a dev dependency — but validate against the published-API snapshot, re-run the inventory diff, and re-check the changelog for behavioural changes (as in this release) before bumping across even a patch boundary.
Support. No vendor SLA, one maintainer, draft-PR-only contribution model where maintainers do all merging, tagging and releasing. This release shows the model producing regular, scoped fixes — a positive signal — but throughput still depends on one person, so expect upstream fixes to be slow and plan to vendor a fork or pin a commit if a blocker lands. Posture: treat the version constraint as exact-ish, watch the releases feed, and keep a documented "remove this package" runbook.
Scaling. The package scales with team and agent count, which is the argument for it: placement stops being tribal knowledge. The only scaling cost remains discovery cache (mod:cache / mod:clear) — build-time, not request-time — plus case-sensitivity behaviour across macOS, Windows and Linux, which the CI matrix suggests is taken seriously but which we should still spot-check on a mixed-OS team. The new state reporting scales well in agent-heavy workflows because it lets automated re-runs converge on unchanged rather than blindly rewriting.
Failure modes to instrument.
| Failure | Signal | Mitigation |
|---|---|---|
| Files written into a non-autoloaded root | Generation warns but still writes | Fail CI on class not found; make mod:autoload mandatory post-layout-change |
composer.json mapping conflict |
mod:autoload refuses |
--dry-run gate in CI; resolve mappings by hand |
Agent uses make:* instead of mod:* |
Files land in app/ |
Instruct via AGENTS.md/skill; code-review checklist; inventory diff |
| Custom layout drift after upgrade | extends() re-copies parent semantics |
Pin version; re-run mod:list --json diff; snapshot tests |
| Scaffold silently diverges on re-run | state: edited where unchanged expected (v0.7.2+) |
CI gate on state; review edited diffs like hand-written code |
| Placeholder-warning behaviour change breaks tooling | Parsed dry-run text changes between patches | Parse --json only; never scrape human output in CI |
| Stale dry-run plan | --json plan written against older HEAD |
Re-run preview immediately before the real command |
| Migration identity/timestamp mismatch across scaffold parts | Duplicate or misnamed migrations | Resolved in v0.7.2 (#165); pin ≥ 0.7.2 before adopting tree scaffolds with migrations |
Case-only folder collision (API Resources vs resources/) |
Compilation check | Move frontend paths under ui/ as documented |
| Maintainer abandonment | No releases against new Laravel majors | Dev-only dependency; fork-and-patch runbook owned by platform team |
| Upstream review workflows leak diffs | Vendor questionnaire gap | Confirm OpenCode/LongCat data handling; do not enable comment-triggered review on our mirrors |
Ramp-up. Low and slightly improved. A developer who knows make:* needs roughly a half-day to be productive, and the real training artefact is not the tool but the layout decision, written down once and derived from mod:list --json. The dry-run state labels make the re-run story easier to teach: "preview, read state, then write." For agents, onboarding is a single skill file plus a rule: inventory, preview, then write. Budget one engineer-week end-to-end for install, pilot domain, CI gates and the written "where files go" standard — and treat the pilot's exit criteria as measured review friction and agent misplacement rate on one domain, plus a stable unchanged rate on scaffold re-runs, rather than "package installed."
How can I help you explore Laravel packages today?