VSA — Slice Placement & Isolation
The architecture in one breath: group by feature slice, not file type; slices never import each other; modules talk only through database state and events; the scheduler is the only orchestrator.
Default mode is place; the words "audit", "scan", "violations", "leak" select audit.
The Rules (both modes enforce these)
- Slice = one user story. Every class serving that story — controller, service, job, listener — lives in the same folder. Namespace:
App\{Module}\{Slice}\{ClassName}.
- No type folders.
Actions/, Jobs/, Listeners/, Http/Controllers/ are banned; the moment one appears, slicing has failed.
- No cross-slice imports. A slice never
uses or dispatches a class from another slice.
- Shared within a module → module root. A class, DTO, or enum used by 2+ slices of the same module moves to
app/{Module}/ — never defined in one slice and imported by another.
- Events are the seams.
- Slice must trigger a sibling slice → fire a module-internal event at the module root; the receiving slice registers a listener. Never a direct call or job dispatch across the boundary.
- Event consumed by 2+ modules →
App\Shared\Events\.
- An event never lives inside a slice folder.
- Modules never import each other's classes. Cross-module effects flow through database state (status fields another module's scheduler picks up) or shared events. Models used by many modules live in
App\Shared\.
- The scheduler orchestrates. Module A writes state; the scheduler detects it and triggers Module B. No module calls another.
Mode PLACE
Given a new class, feature, or whole module:
- Name the user story. One story → one slice; two stories → two slices, even if they share a screen.
- Walk the decision ladder: used by one slice → in the slice · by 2+ slices, one module → module root · by 2+ modules →
Shared/ · event → rule 5 placement.
- For a new module, design the map with aggregate-root thinking before folders: trigger → state written → who consumes it. Deliver a table (slice → story → classes) plus the module's status-field contract (values written, consumed by whom).
- Verify against reality: read the existing tree first; place new code the way its neighbors are placed. If existing structure already violates the rules, flag it — do not imitate a violation.
The rules are stack-agnostic — folder-per-slice with event seams works in any framework; only the namespace idiom changes.
Mode AUDIT
Sweep the module (or whole app) for each violation class, with grep evidence:
- Cross-slice
use/dispatch statements.
- Cross-module class imports (anything not via
Shared/ or events).
- Direct cross-module job dispatches or method calls.
- Events defined inside slice folders.
- DTOs/enums defined in one slice, imported by another.
- Type folders reappearing.
- Orchestration outside the scheduler (module A directly triggering module B).
Output — per violation
file:line → rule broken → the concrete move that fixes it ("move ParsedRowDto to app/Ingestion/, update 3 imports"; "replace the direct dispatch with a CsvUploadParsedEvent at module root + listener in the receiving slice"). Ranked: cross-module coupling first (breaks the architecture), cosmetic placement last. End with the isolation verdict per module: clean / leaking.
Audit is read-only; apply moves only on request, each move followed by a green full suite.
1---2name: vsa3description: Vertical Slice Architecture guardian, two modes. PLACE mode: decide where a new class, event, DTO, or module belongs under VSA rules, or design a new module/slice map from scratch. AUDIT mode: scan for slice-isolation violations (cross-slice imports, cross-module coupling, misplaced events) and prescribe the move that fixes each. Use whenever the user says "VSA", "which slice", "where does this class go", "slice isolation", "organize the slices", "cross-slice import", "should this move to module root", or asks how to structure a feature in a slice-based codebase.4---56# VSA — Slice Placement & Isolation78The architecture in one breath: **group by feature slice, not file type; slices never import each other; modules talk only through database state and events; the scheduler is the only orchestrator.**910Default mode is **place**; the words "audit", "scan", "violations", "leak" select **audit**.1112## The Rules (both modes enforce these)13141. **Slice = one user story.** Every class serving that story — controller, service, job, listener — lives in the same folder. Namespace: `App\{Module}\{Slice}\{ClassName}`.152. **No type folders.** `Actions/`, `Jobs/`, `Listeners/`, `Http/Controllers/` are banned; the moment one appears, slicing has failed.163. **No cross-slice imports.** A slice never `use`s or dispatches a class from another slice.174. **Shared within a module → module root.** A class, DTO, or enum used by 2+ slices of the same module moves to `app/{Module}/` — never defined in one slice and imported by another.185. **Events are the seams.**19 - Slice must trigger a sibling slice → fire a module-internal event at the **module root**; the receiving slice registers a listener. Never a direct call or job dispatch across the boundary.20 - Event consumed by 2+ modules → `App\Shared\Events\`.21 - An event never lives inside a slice folder.226. **Modules never import each other's classes.** Cross-module effects flow through database state (status fields another module's scheduler picks up) or shared events. Models used by many modules live in `App\Shared\`.237. **The scheduler orchestrates.** Module A writes state; the scheduler detects it and triggers Module B. No module calls another.2425## Mode PLACE2627Given a new class, feature, or whole module:28291. Name the user story. One story → one slice; two stories → two slices, even if they share a screen.302. Walk the decision ladder: used by one slice → in the slice · by 2+ slices, one module → module root · by 2+ modules → `Shared/` · event → rule 5 placement.313. For a new module, design the map with aggregate-root thinking before folders: **trigger → state written → who consumes it**. Deliver a table (slice → story → classes) plus the module's status-field contract (values written, consumed by whom).324. Verify against reality: read the existing tree first; place new code the way its neighbors are placed. If existing structure already violates the rules, flag it — do not imitate a violation.3334The rules are stack-agnostic — folder-per-slice with event seams works in any framework; only the namespace idiom changes.3536## Mode AUDIT3738Sweep the module (or whole app) for each violation class, with grep evidence:39401. Cross-slice `use`/dispatch statements.412. Cross-module class imports (anything not via `Shared/` or events).423. Direct cross-module job dispatches or method calls.434. Events defined inside slice folders.445. DTOs/enums defined in one slice, imported by another.456. Type folders reappearing.467. Orchestration outside the scheduler (module A directly triggering module B).4748### Output — per violation4950`file:line` → rule broken → **the concrete move that fixes it** ("move `ParsedRowDto` to `app/Ingestion/`, update 3 imports"; "replace the direct dispatch with a `CsvUploadParsedEvent` at module root + listener in the receiving slice"). Ranked: cross-module coupling first (breaks the architecture), cosmetic placement last. End with the isolation verdict per module: clean / leaking.5152Audit is read-only; apply moves only on request, each move followed by a green full suite.