<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>


<command-name>/plugin</command-name>
            <command-message>plugin</command-message>
            <command-args></command-args>


<local-command-stdout>(no content)</local-command-stdout>


Your mission is to implement an entire shop system based on the specifications im specs/*. Distribute work to sub-agents. Keep the context of the main agent clean. You have to build in one go without stopping. You hace to use team mode! You have to test everything via Pest (unit, and functional tests). You have to also additional simulate user behaviour using the Playwright MPC and confirm that all acceptance criterias are met. If you find bugs, you have to fix them. The result is a perfect shop system. All requirements are perfectly implemented. All acceptance criterias are met, tested and confirmed by you.

Continuously keep track of the progress in specs/progress.md Commit your progress after every relevant iteration with a meaningful message.

When implementation is fully done, then make a full review meeting with Playwright in Chrome and showcase all features (customer- and admin-side) to me. In case bugs appear, you have to fix them all and restart the review meeting. Shop is running at http://shop.test/.

Don't re-use any existing implementation in another branch. Build it from scratch.


## laravel-boost
Laravel ecosystem MCP server offering database schema access, error logs, semantic documentation search and more. Boost helps with code generation.


- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, use Config tool.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- simplify: Review changed code for reuse, quality, and efficiency, then fix any issues found.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled remote agents (triggers) that execute on a cron schedule. - When the user wants to schedule a recurring remote agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/triggers.
- claude-api: Build, debug, and optimize Claude API / Anthropic SDK apps. Apps built with this skill should include prompt caching. Also handles migrating existing Claude API code between Claude model versions (4.5 → 4.6, 4.6 → 4.7, retired-model replacements).
TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`; user asks for the Claude API, Anthropic SDK, or Managed Agents; user adds/modifies/tunes a Claude feature (caching, thinking, compaction, tool use, batch, files, citations, memory) or model (Opus/Sonnet/Haiku) in a file; questions about prompt caching / cache hit rate in an Anthropic SDK project.
SKIP: file imports `openai`/other-provider SDK, filename like `*-openai.py`/`*-generic.py`, provider-neutral code, general programming/ML.
- spec-to-cli: Build a polished TypeScript ESM CLI from an OpenAPI/Swagger spec using codegen plus the fellowship team-mode orchestration pattern. Trigger when the user asks to build a CLI that wraps a REST API, generate commands from swagger/openapi, or replicate the finanzfluss-cli architecture.
- frontend-design: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a pull request
- security-review: Complete a security review of the pending changes on the current branch


I'll start by reading the team-mode documentation and the specs to understand the scope, then set up team mode with a task list.


ToolSearch

Question: select:WebFetch,TaskCreate,TaskList,TaskUpdate,TaskGet,TaskOutput,TaskStop,TeamCreate,TeamDelete,SendMessage,EnterPlanMode,ExitPlanMode,AskUserQuestion,Monitor


WebFetch TaskCreate TaskList TaskUpdate TaskGet TaskOutput TaskStop TeamCreate TeamDelete SendMessage EnterPlanMode ExitPlanMode AskUserQuestion Monitor

Question: select:WebFetch,TaskCreate,TaskList,TaskUpdate,TaskGet,TaskOutput,TaskStop,TeamCreate,TeamDelete,SendMessage,EnterPlanMode,ExitPlanMode,AskUserQuestion,Monitor


<command-name>/sandbox</command-name>
            <command-message>sandbox</command-message>
            <command-args></command-args>


<local-command-stdout>○ Sandbox disabled</local-command-stdout>


WebFetch

Prompt: How does team mode work in Claude Code? How do I create teams, spawn teammates, assign tasks, coordinate work? Explain the full workflow and all protocol messages.


> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Orchestrate teams of Claude Code sessions

> Coordinate multiple Claude Code instances working together as a team, with shared tasks, inter-agent messaging, and centralized management.

<Warning>
  Agent teams are experimental and disabled by default. Enable them by adding `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` to your [settings.json](/en/settings) or environment. Agent teams have [known limitations](#limitations) around session resumption, task coordination, and shutdown behavior.
</Warning>

Agent teams let you coordinate multiple Claude Code instances working together. One session acts as the team lead, coordinating work, assigning tasks, and synthesizing results. Teammates work independently, each in its own context window, and communicate directly with each other.

Unlike [subagents](/en/sub-agents), which run within a single session and can only report back to the main agent, you can also interact with individual teammates directly without going through the lead.

<Note>
  Agent teams require Claude Code v2.1.32 or later. Check your version with `claude --version`.
</Note>

This page covers:

* [When to use agent teams](#when-to-use-agent-teams), including best use cases and how they compare with subagents
* [Starting a team](#start-your-first-agent-team)
* [Controlling teammates](#control-your-agent-team), including display modes, task assignment, and delegation
* [Best practices for parallel work](#best-practices)

## When to use agent teams

Agent teams are most effective for tasks where parallel exploration adds real value. See [use case examples](#use-case-examples) for full scenarios. The strongest use cases are:

* **Research and review**: multiple teammates can investigate different aspects of a problem simultaneously, then share and challenge each other's findings
* **New modules or features**: teammates can each own a separate piece without stepping on each other
* **Debugging with competing hypotheses**: teammates test different theories in parallel and converge on the answer faster
* **Cross-layer coordination**: changes that span frontend, backend, and tests, each owned by a different teammate

Agent teams add coordination overhead and use significantly more tokens than a single session. They work best when teammates can operate independently. For sequential tasks, same-file edits, or work with many dependencies, a single session or [subagents](/en/sub-agents) are more effective.

### Compare with subagents

Both agent teams and [subagents](/en/sub-agents) let you parallelize work, but they operate differently. Choose based on whether your workers need to communicate with each other:

<Frame caption="Subagents only report results back to the main agent and never talk to each other. In agent teams, teammates share a task list, claim work, and communicate directly with each other.">
  <img src="https://mintcdn.com/claude-code/nsvRFSDNfpSU5nT7/images/subagents-vs-agent-teams-light.png?fit=max&auto=format&n=nsvRFSDNfpSU5nT7&q=85&s=2f8db9b4f3705dd3ab931fbe2d96e42a" className="dark:hidden" alt="Diagram comparing subagent and agent team architectures. Subagents are spawned by the main agent, do work, and report results back. Agent teams coordinate through a shared task list, with teammates communicating directly with each other." width="4245" height="1615" data-path="images/subagents-vs-agent-teams-light.png" />

  <img src="https://mintcdn.com/claude-code/nsvRFSDNfpSU5nT7/images/subagents-vs-agent-teams-dark.png?fit=max&auto=format&n=nsvRFSDNfpSU5nT7&q=85&s=d573a037540f2ada6a9ae7d8285b46fd" className="hidden dark:block" alt="Diagram comparing subagent and agent team architectures. Subagents are spawned by the main agent, do work, and report results back. Agent teams coordinate through a shared task list, with teammates communicating directly with each other." width="4245" height="1615" data-path="images/subagents-vs-agent-teams-dark.png" />
</Frame>

|                   | Subagents                                        | Agent teams                                         |
| :---------------- | :----------------------------------------------- | :-------------------------------------------------- |
| **Context**       | Own context window; results return to the caller | Own context window; fully independent               |
| **Communication** | Report results back to the main agent only       | Teammates message each other directly               |
| **Coordination**  | Main agent manages all work                      | Shared task list with self-coordination             |
| **Best for**      | Focused tasks where only the result matters      | Complex work requiring discussion and collaboration |
| **Token cost**    | Lower: results summarized back to main context   | Higher: each teammate is a separate Claude instance |

Use subagents when you need quick, focused workers that report back. Use agent teams when teammates need to share findings, challenge each other, and coordinate on their own.

## Enable agent teams

Agent teams are disabled by default. Enable them by setting the `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` environment variable to `1`, either in your shell environment or through [settings.json](/en/settings):

```json settings.json theme={null}
{
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  }
}
```

## Start your first agent team

After enabling agent teams, tell Claude to create an agent team and describe the task and the team structure you want in natural language. Claude creates the team, spawns teammates, and coordinates work based on your prompt.

This example works well because the three roles are independent and can explore the problem without waiting on each other:

```text theme={null}
I'm designing a CLI tool that helps developers track TODO comments across
their codebase. Create an agent team to explore this from different angles: one
teammate on UX, one on technical architecture, one playing devil's advocate.
```

From there, Claude creates a team with a [shared task list](/en/interactive-mode#task-list), spawns teammates for each perspective, has them explore the problem, synthesizes findings, and attempts to [clean up the team](#clean-up-the-team) when finished.

The lead's terminal lists all teammates and what they're working on. Use Shift+Down to cycle through teammates and message them directly. After the last teammate, Shift+Down wraps back to the lead.

If you want each teammate in its own split pane, see [Choose a display mode](#choose-a-display-mode).

## Control your agent team

Tell the lead what you want in natural language. It handles team coordination, task assignment, and delegation based on your instructions.

### Choose a display mode

Agent teams support two display modes:

* **In-process**: all teammates run inside your main terminal. Use Shift+Down to cycle through teammates and type to message them directly. Works in any terminal, no extra setup required.
* **Split panes**: each teammate gets its own pane. You can see everyone's output at once and click into a pane to interact directly. Requires tmux, or iTerm2.

<Note>
  `tmux` has known limitations on certain operating systems and traditionally works best on macOS. Using `tmux -CC` in iTerm2 is the suggested entrypoint into `tmux`.
</Note>

The default is `"auto"`, which uses split panes if you're already running inside a tmux session, and in-process otherwise. The `"tmux"` setting enables split-pane mode and auto-detects whether to use tmux or iTerm2 based on your terminal. To override, set `teammateMode` in your [global config](/en/settings#global-config-settings) at `~/.claude.json`:

```json theme={null}
{
  "teammateMode": "in-process"
}
```

To force in-process mode for a single session, pass it as a flag:

```bash theme={null}
claude --teammate-mode in-process
```

Split-pane mode requires either [tmux](https://github.com/tmux/tmux/wiki) or iTerm2 with the [`it2` CLI](https://github.com/mkusaka/it2). To install manually:

* **tmux**: install through your system's package manager. See the [tmux wiki](https://github.com/tmux/tmux/wiki/Installing) for platform-specific instructions.
* **iTerm2**: install the [`it2` CLI](https://github.com/mkusaka/it2), then enable the Python API in **iTerm2 → Settings → General → Magic → Enable Python API**.

### Specify teammates and models

Claude decides the number of teammates to spawn based on your task, or you can specify exactly what you want:

```text theme={null}
Create a team with 4 teammates to refactor these modules in parallel.
Use Sonnet for each teammate.
```

### Require plan approval for teammates

For complex or risky tasks, you can require teammates to plan before implementing. The teammate works in read-only plan mode until the lead approves their approach:

```text theme={null}
Spawn an architect teammate to refactor the authentication module.
Require plan approval before they make any changes.
```

When a teammate finishes planning, it sends a plan approval request to the lead. The lead reviews the plan and either approves it or rejects it with feedback. If rejected, the teammate stays in plan mode, revises based on the feedback, and resubmits. Once approved, the teammate exits plan mode and begins implementation.

The lead makes approval decisions autonomously. To influence the lead's judgment, give it criteria in your prompt, such as "only approve plans that include test coverage" or "reject plans that modify the database schema."

### Talk to teammates directly

Each teammate is a full, independent Claude Code session. You can message any teammate directly to give additional instructions, ask follow-up questions, or redirect their approach.

* **In-process mode**: use Shift+Down to cycle through teammates, then type to send them a message. Press Enter to view a teammate's session, then Escape to interrupt their current turn. Press Ctrl+T to toggle the task list.
* **Split-pane mode**: click into a teammate's pane to interact with their session directly. Each teammate has a full view of their own terminal.

### Assign and claim tasks

The shared task list coordinates work across the team. The lead creates tasks and teammates work through them. Tasks have three states: pending, in progress, and completed. Tasks can also depend on other tasks: a pending task with unresolved dependencies cannot be claimed until those dependencies are completed.

The lead can assign tasks explicitly, or teammates can self-claim:

* **Lead assigns**: tell the lead which task to give to which teammate
* **Self-claim**: after finishing a task, a teammate picks up the next unassigned, unblocked task on its own

Task claiming uses file locking to prevent race conditions when multiple teammates try to claim the same task simultaneously.

### Shut down teammates

To gracefully end a teammate's session:

```text theme={null}
Ask the researcher teammate to shut down
```

The lead sends a shutdown request. The teammate can approve, exiting gracefully, or reject with an explanation.

### Clean up the team

When you're done, ask the lead to clean up:

```text theme={null}
Clean up the team
```

This removes the shared team resources. When the lead runs cleanup, it checks for active teammates and fails if any are still running, so shut them down first.

<Warning>
  Always use the lead to clean up. Teammates should not run cleanup because their team context may not resolve correctly, potentially leaving resources in an inconsistent state.
</Warning>

### Enforce quality gates with hooks

Use [hooks](/en/hooks) to enforce rules when teammates finish work or tasks are created or completed:

* [`TeammateIdle`](/en/hooks#teammateidle): runs when a teammate is about to go idle. Exit with code 2 to send feedback and keep the teammate working.
* [`TaskCreated`](/en/hooks#taskcreated): runs when a task is being created. Exit with code 2 to prevent creation and send feedback.
* [`TaskCompleted`](/en/hooks#taskcompleted): runs when a task is being marked complete. Exit with code 2 to prevent completion and send feedback.

## How agent teams work

This section covers the architecture and mechanics behind agent teams. If you want to start using them, see [Control your agent team](#control-your-agent-team) above.

### How Claude starts agent teams

There are two ways agent teams get started:

* **You request a team**: give Claude a task that benefits from parallel work and explicitly ask for an agent team. Claude creates one based on your instructions.
* **Claude proposes a team**: if Claude determines your task would benefit from parallel work, it may suggest creating a team. You confirm before it proceeds.

In both cases, you stay in control. Claude won't create a team without your approval.

### Architecture

An agent team consists of:

| Component     | Role                                                                                |
| :------------ | :----------------------------------------------------------------------------------------- |
| **Team lead** | The main Claude Code session that creates the team, spawns teammates, and coordinates work |
| **Teammates** | Separate Claude Code instances that each work on assigned tasks                            |
| **Task list** | Shared list of work items that teammates claim and complete                                |
| **Mailbox**   | Messaging system for communication between agents                                          |

See [Choose a display mode](#choose-a-display-mode) for display configuration options. Teammate messages arrive at the lead automatically.

The system manages task dependencies automatically. When a teammate completes a task that other tasks depend on, blocked tasks unblock without manual intervention.

Teams and tasks are stored locally:

* **Team config**: `~/.claude/teams/{team-name}/config.json`
* **Task list**: `~/.claude/tasks/{team-name}/`

Claude Code generates both of these automatically when you create a team and updates them as teammates join, go idle, or leave. The team config holds runtime state such as session IDs and tmux pane IDs, so don't edit it by hand or pre-author it: your changes are overwritten on the next state update.

To define reusable teammate roles, use [subagent definitions](#use-subagent-definitions-for-teammates) instead.

The team config contains a `members` array with each teammate's name, agent ID, and agent type. Teammates can read this file to discover other team members.

There is no project-level equivalent of the team config. A file like `.claude/teams/teams.json` in your project directory is not recognized as configuration; Claude treats it as an ordinary file.

### Use subagent definitions for teammates

When spawning a teammate, you can reference a [subagent](/en/sub-agents) type from any [subagent scope](/en/sub-agents#choose-the-subagent-scope): project, user, plugin, or CLI-defined. This lets you define a role once, such as a security-reviewer or test-runner, and reuse it both as a delegated subagent and as an agent team teammate.

To use a subagent definition, mention it by name when asking Claude to spawn the teammate:

```text theme={null}
Spawn a teammate using the security-reviewer agent type to audit the auth module.
```

The teammate honors that definition's `tools` allowlist and `model`, and the definition's body is appended to the teammate's system prompt as additional instructions rather than replacing it. Team coordination tools such as `SendMessage` and the task management tools are always available to a teammate even when `tools` restricts other tools.

<Note>
  The `skills` and `mcpServers` frontmatter fields in a subagent definition are not applied when that definition runs as a teammate. Teammates load skills and MCP servers from your project and user settings, the same as a regular session.
</Note>

### Permissions

Teammates start with the lead's permission settings. If the lead runs with `--dangerously-skip-permissions`, all teammates do too. After spawning, you can change individual teammate modes, but you can't set per-teammate modes at spawn time.

### Context and communication

Each teammate has its own context window. When spawned, a teammate loads the same project context as a regular session: CLAUDE.md, MCP servers, and skills. It also receives the spawn prompt from the lead. The lead's conversation history does not carry over.

**How teammates share information:**

* **Automatic message delivery**: when teammates send messages, they're delivered automatically to recipients. The lead doesn't need to poll for updates.
* **Idle notifications**: when a teammate finishes and stops, they automatically notify the lead.
* **Shared task list**: all agents can see task status and claim available work.

**Teammate messaging:**

* **message**: send a message to one specific teammate
* **broadcast**: send to all teammates simultaneously. Use sparingly, as costs scale with team size.

The lead assigns every teammate a name when it spawns them, and any teammate can message any other by that name. To get predictable names you can reference in later prompts, tell the lead what to call each teammate in your spawn instruction.

### Token usage

Agent teams use significantly more tokens than a single session. Each teammate has its own context window, and token usage scales with the number of active teammates. For research, review, and new feature work, the extra tokens are usually worthwhile. For routine tasks, a single session is more cost-effective. See [agent team token costs](/en/costs#agent-team-token-costs) for usage guidance.

## Use case examples

These examples show how agent teams handle tasks where parallel exploration adds value.

### Run a parallel code review

A single reviewer tends to gravitate toward one type of issue at a time. Splitting review criteria into independent domains means security, performance, and test coverage all get thorough attention simultaneously. The prompt assigns each teammate a distinct lens so they don't overlap:

```text theme={null}
Create an agent team to review PR #142. Spawn three reviewers:
- One focused on security implications
- One checking performance impact
- One validating test coverage
Have them each review and report findings.
```

Each reviewer works from the same PR but applies a different filter. The lead synthesizes findings across all three after they finish.

### Investigate with competing hypotheses

When the root cause is unclear, a single agent tends to find one plausible explanation and stop looking. The prompt fights this by making teammates explicitly adversarial: each one's job is not only to investigate its own theory but to challenge the others'.

```text theme={null}
Users report the app exits after one message instead of staying connected.
Spawn 5 agent teammates to investigate different hypotheses. Have them talk to
each other to try to disprove each other's theories, like a scientific
debate. Update the findings doc with whatever consensus emerges.
```

The debate structure is the key mechanism here. Sequential investigation suffers from anchoring: once one theory is explored, subsequent investigation is biased toward it.

With multiple independent investigators actively trying to disprove each other, the theory that survives is much more likely to be the actual root cause.

## Best practices

### Give teammates enough context

Teammates load project context automatically, including CLAUDE.md, MCP servers, and skills, but they don't inherit the lead's conversation history. See [Context and communication](#context-and-communication) for details. Include task-specific details in the spawn prompt:

```text theme={null}
Spawn a security reviewer teammate with the prompt: "Review the authentication module
at src/auth/ for security vulnerabilities. Focus on token handling, session
management, and input validation. The app uses JWT tokens stored in
httpOnly cookies. Report any issues with severity ratings."
```

### Choose an appropriate team size

There's no hard limit on the number of teammates, but practical constraints apply:

* **Token costs scale linearly**: each teammate has its own context window and consumes tokens independently. See [agent team token costs](/en/costs#agent-team-token-costs) for details.
* **Coordination overhead increases**: more teammates means more communication, task coordination, and potential for conflicts
* **Diminishing returns**: beyond a certain point, additional teammates don't speed up work proportionally

Start with 3-5 teammates for most workflows. This balances parallel work with manageable coordination. The examples in this guide use 3-5 teammates because that range works well across different task types.

Having 5-6 [tasks](/en/agent-teams#architecture) per teammate keeps everyone productive without excessive context switching. If you have 15 independent tasks, 3 teammates is a good starting point.

Scale up only when the work genuinely benefits from having teammates work simultaneously. Three focused teammates often outperform five scattered ones.

### Size tasks appropriately

* **Too small**: coordination overhead exceeds the benefit
* **Too large**: teammates work too long without check-ins, increasing risk of wasted effort
* **Just right**: self-contained units that produce a clear deliverable, such as a function, a test file, or a review

<Tip>
  The lead breaks work into tasks and assigns them to teammates automatically. If it isn't creating enough tasks, ask it to split the work into smaller pieces. Having 5-6 tasks per teammate keeps everyone productive and lets the lead reassign work if someone gets stuck.
</Tip>

### Wait for teammates to finish

Sometimes the lead starts implementing tasks itself instead of waiting for teammates. If you notice this:

```text theme={null}
Wait for your teammates to complete their tasks before proceeding
```

### Start with research and review

If you're new to agent teams, start with tasks that have clear boundaries and don't require writing code: reviewing a PR, researching a library, or investigating a bug. These tasks show the value of parallel exploration without the coordination challenges that come with parallel implementation.

### Avoid file conflicts

Two teammates editing the same file leads to overwrites. Break the work so each teammate owns a different set of files.

### Monitor and steer

Check in on teammates' progress, redirect approaches that aren't working, and synthesize findings as they come in. Letting a team run unattended for too long increases the risk of wasted effort.

## Troubleshooting

### Teammates not appearing

If teammates aren't appearing after you ask Claude to create a team:

* In in-process mode, teammates may already be running but not visible. Press Shift+Down to cycle through active teammates.
* Check that the task you gave Claude was complex enough to warrant a team. Claude decides whether to spawn teammates based on the task.
* If you explicitly requested split panes, ensure tmux is installed and available in your PATH:
  ```bash theme={null}
  which tmux
  ```
* For iTerm2, verify the `it2` CLI is installed and the Python API is enabled in iTerm2 preferences.

### Too many permission prompts

Teammate permission requests bubble up to the lead, which can create friction. Pre-approve common operations in your [permission settings](/en/permissions) before spawning teammates to reduce interruptions.

### Teammates stopping on errors

Teammates may stop after encountering errors instead of recovering. Check their output using Shift+Down in in-process mode or by clicking the pane in split mode, then either:

* Give them additional instructions directly
* Spawn a replacement teammate to continue the work

### Lead shuts down before work is done

The lead may decide the team is finished before all tasks are actually complete. If this happens, tell it to keep going. You can also tell the lead to wait for teammates to finish before proceeding if it starts doing work instead of delegating.

### Orphaned tmux sessions

If a tmux session persists after the team ends, it may not have been fully cleaned up. List sessions and kill the one created by the team:

```bash theme={null}
tmux ls
tmux kill-session -t <session-name>
```

## Limitations

Agent teams are experimental. Current limitations to be aware of:

* **No session resumption with in-process teammates**: `/resume` and `/rewind` do not restore in-process teammates. After resuming a session, the lead may attempt to message teammates that no longer exist. If this happens, tell the lead to spawn new teammates.
* **Task status can lag**: teammates sometimes fail to mark tasks as completed, which blocks dependent tasks. If a task appears stuck, check whether the work is actually done and update the task status manually or tell the lead to nudge the teammate.
* **Shutdown can be slow**: teammates finish their current request or tool call before shutting down, which can take time.
* **One team per session**: a lead can only manage one team at a time. Clean up the current team before starting a new one.
* **No nested teams**: teammates cannot spawn their own teams or teammates. Only the lead can manage the team.
* **Lead is fixed**: the session that creates the team is the lead for its lifetime. You can't promote a teammate to lead or transfer leadership.
* **Permissions set at spawn**: all teammates start with the lead's permission mode. You can change individual teammate modes after spawning, but you can't set per-teammate modes at spawn time.
* **Split panes require tmux or iTerm2**: the default in-process mode works in any terminal. Split-pane mode isn't supported in VS Code's integrated terminal, Windows Terminal, or Ghostty.

<Tip>
  **`CLAUDE.md` works normally**: teammates read `CLAUDE.md` files from their working directory. Use this to provide project-specific guidance to all teammates.
</Tip>

## Next steps

Explore related approaches for parallel work and delegation:

* **Lightweight delegation**: [subagents](/en/sub-agents) spawn helper agents for research or verification within your session, better for tasks that don't need inter-agent coordination
* **Manual parallel sessions**: [Git worktrees](/en/common-workflows#run-parallel-claude-code-sessions-with-git-worktrees) let you run multiple Claude Code sessions yourself without automated team coordination
* **Compare approaches**: see the [subagent vs agent team](/en/features-overview#compare-similar-features) comparison for a side-by-side breakdown


Bash

List all spec files


Error: File content (45662 tokens) exceeds maximum allowed tokens (25000). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.


total 1456
drwxr-xr-x  11 fabianwesner  staff     352 Apr 18 09:10 .
drwxr-xr-x@ 42 fabianwesner  staff    1344 Apr 18 09:12 ..
-rw-r--r--@  1 fabianwesner  staff   69521 Feb 11 19:43 01-DATABASE-SCHEMA.md
-rw-r--r--@  1 fabianwesner  staff   89763 Feb 12 19:35 02-API-ROUTES.md
-rw-r--r--@  1 fabianwesner  staff  102829 Feb 12 19:35 03-ADMIN-UI.md
-rw-r--r--@  1 fabianwesner  staff   74582 Feb 12 19:35 04-STOREFRONT-UI.md
-rw-r--r--@  1 fabianwesner  staff   68936 Feb 12 19:35 05-BUSINESS-LOGIC.md
-rw-r--r--@  1 fabianwesner  staff   50342 Feb 12 19:35 06-AUTH-AND-SECURITY.md
-rw-r--r--@  1 fabianwesner  staff   69428 Feb 12 19:35 07-SEEDERS-AND-TEST-DATA.md
-rw-r--r--@  1 fabianwesner  staff   88099 Feb 12 19:35 08-PLAYWRIGHT-E2E-PLAN.md
-rw-r--r--@  1 fabianwesner  staff  116810 Feb 12 19:35 09-IMPLEMENTATION-ROADMAP.md


Your mission is to implement an entire shop system based on the specifications im specs/*. Distribute work to sub-agents. Keep the context of the main agent clean. You have to build in one go without…


ls -la /Users/fabianwesner/Herd/shop/ && echo "---" && ls /Users/fabianwesner/Herd/shop/app/ 2>/dev/null || echo "no app folder"

Check shop directory structure


total 1136
drwxr-xr-x@  42 fabianwesner  staff    1344 Apr 18 09:12 .
drwxr-xr-x@ 123 fabianwesner  staff    3936 Apr 16 07:10 ..
drwxr-xr-x@   3 fabianwesner  staff      96 Apr 17 21:38 .agents
drwxr-xr-x@   3 fabianwesner  staff      96 Apr 18 09:17 .claude
drwxr-xr-x@   4 fabianwesner  staff     128 Feb 12 19:35 .cursor
-rw-r--r--@   1 fabianwesner  staff    6148 Feb 12 15:19 .DS_Store
-rw-r--r--@   1 fabianwesner  staff     252 Feb  4 19:45 .editorconfig
-rw-r--r--@   1 fabianwesner  staff    3403 Mar 19 18:42 .env
-rw-r--r--@   1 fabianwesner  staff    1086 Feb 11 12:21 .env.example
drwxr-xr-x@  18 fabianwesner  staff     576 Apr 18 09:13 .git
-rw-r--r--@   1 fabianwesner  staff     210 Feb  4 19:45 .gitattributes
drwxr-xr-x@   4 fabianwesner  staff     128 Apr 17 21:38 .github
-rw-r--r--    1 fabianwesner  staff     286 Apr 18 09:10 .gitignore
drwxr-xr-x@  13 fabianwesner  staff     416 Apr 18 09:12 .idea
drwxr-xr-x@   3 fabianwesner  staff      96 Feb 12 19:35 .junie
-rw-r--r--@   1 fabianwesner  staff     455 Apr 17 21:36 .mcp.json
drwxr-xr-x@   3 fabianwesner  staff      96 Apr 13 11:44 .vscode
-rw-r--r--@   1 fabianwesner  staff   15545 Apr 17 21:38 AGENTS.md
drwxr-xr-x@  10 fabianwesner  staff     320 Apr 18 09:10 app
-rwxr-xr-x@   1 fabianwesner  staff     425 Feb  4 19:45 artisan
-rw-r--r--@   1 fabianwesner  staff     365 Apr 17 21:38 boost.json
drwxr-xr-x@   5 fabianwesner  staff     160 Apr 18 09:10 bootstrap
-rw-r--r--@   1 fabianwesner  staff   20750 Apr 17 21:36 CLAUDE.md
-rw-r--r--    1 fabianwesner  staff    3216 Apr 18 09:10 composer.json
-rw-r--r--    1 fabianwesner  staff  368076 Apr 18 09:10 composer.lock
drwxr-xr-x@  13 fabianwesner  staff     416 Apr 18 09:10 config
drwxr-xr-x@   6 fabianwesner  staff     192 Apr 18 09:11 database
drwxr-xr-x@  91 fabianwesner  staff    2912 Mar 16 11:52 node_modules
-rw-r--r--@   1 fabianwesner  staff     481 Apr 17 21:38 opencode.json
-rw-r--r--    1 fabianwesner  staff   98376 Mar 16 14:22 package-lock.json
-rw-r--r--    1 fabianwesner  staff     633 Mar 16 14:22 package.json
-rw-r--r--    1 fabianwesner  staff    1284 Apr 18 09:10 phpunit.xml
-rw-r--r--@   1 fabianwesner  staff      28 Apr 17 21:36 pint.json
drwxr-xr-x@  11 fabianwesner  staff     352 Apr 17 22:26 public
-rw-r--r--@   1 fabianwesner  staff    1111 Apr 18 09:12 README.md
drwxr-xr-x@   5 fabianwesner  staff     160 Feb  4 19:45 resources
drwxr-xr-x@   5 fabianwesner  staff     160 Apr 18 09:10 routes
drwxr-xr-x   11 fabianwesner  staff     352 Apr 18 09:10 specs
drwxr-xr-x@   6 fabianwesner  staff     192 Mar 13 17:36 storage
drwxr-xr-x@   6 fabianwesner  staff     192 Apr 18 09:10 tests
drwxr-xr-x@  59 fabianwesner  staff    1888 Apr 17 21:55 vendor
-rw-r--r--@   1 fabianwesner  staff     460 Feb  4 19:45 vite.config.js
---
Actions
Concerns
Http
Livewire
Models
Providers
Services
Support


1	# 09 - Implementation Roadmap and Pest Test Specification
2
3	> E-commerce platform - Step-by-step build order and exhaustive test plan for Laravel 12 + Livewire v4 + Flux UI Free + SQLite + Tailwind CSS v4
4
5	---
6
7	## 1. Technology Decisions (Final)
8
9	These decisions adapt the original PDF spec for a self-contained, benchmarkable Laravel application. All decisions are final and must not be revisited.
10
11	| Concern | PDF Default | Our Decision | Rationale |
12	|---------|-------------|--------------|-----------|
13	| Database | PostgreSQL | SQLite | Self-contained, no external DB, zero config |
14	| Cache | Redis | File (Array for tests) | No Redis dependency |
15	| Session | Redis | File | No Redis dependency |
16	| Queue | Redis + Horizon | Sync (dev) / Database (prod) | No Redis dependency, sync for immediate feedback |
17	| Search | Elasticsearch + Scout | SQLite FTS5 | Self-contained, no external service |
18	| Storage | S3 | Local filesystem (storage/app/public) | No AWS dependency |
19	| Admin UI | React/Vue SPA | Livewire v4 + Flux UI Free | Server-rendered, matches project stack |
20	| Storefront | Blade SSR + Vite | Blade + Livewire + Tailwind v4 | Server-rendered, matches project stack |
21	| Auth (admin) | Sanctum SPA | Laravel session auth | Standard Livewire auth pattern |
22	| Auth (API) | Sanctum tokens | Sanctum personal access tokens | For programmatic API access |
23	| Auth (customer) | Separate guard | Custom `customer` guard | Storefront customer auth |
24	| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
25	| Payments | Stripe + PayPal | Mock PSP (in-process) | No external API calls. MockPaymentProvider handles credit card, PayPal, bank transfer with magic card numbers for testing. |
26	| Email | SMTP/SES | Log driver (dev) / SMTP | Log for benchmark, SMTP configurable |
27	| Monitoring | OpenTelemetry | Laravel Log (structured JSON) | Simplified for benchmark |
28	| CI/CD | GitHub Actions | Not implemented | Out of scope for one-shot benchmark |
29	| Docker | Multi-stage | Not implemented | App served by Laravel Herd locally |
30
31	---
32
33	## 2. Implementation Phases (Build Order)
34
35	Each phase builds on the previous. A coding agent should execute these in strict sequential order. Tests are written alongside each phase (see Section 3 for the full test specification).
36
37	### Phase Dependency Graph
38
39	```mermaid
40	flowchart TD
41	    P1[Phase 1: Foundation]
42	    P2[Phase 2: Catalog]
43	    P3[Phase 3: Themes and Storefront Layout]
44	    P4[Phase 4: Cart, Checkout, Discounts, Shipping, Taxes]
45	    P5[Phase 5: Payments, Orders, Fulfillment]
46	    P6[Phase 6: Customer Accounts]
47	    P7[Phase 7: Admin Panel]
48	    P8[Phase 8: Search]
49	    P9[Phase 9: Analytics]
50	    P10[Phase 10: Apps and Webhooks]
51	    P11[Phase 11: Polish]
52	    P12[Phase 12: Full Test Suite]
53
54	    P1 --> P2
55	    P1 --> P3
56	    P2 --> P4
57	    P3 --> P4
58	    P4 --> P5
59	    P5 --> P6
60	    P5 --> P7
61	    P2 --> P8
62	    P5 --> P9
63	    P5 --> P10
64	    P6 --> P11
65	    P7 --> P11
66	    P8 --> P11
67	    P9 --> P11
68	    P10 --> P11
69	    P11 --> P12
70	```
71
72	---
73
74	### Phase 1: Foundation (Migrations, Models, Middleware, Auth)
75
76	**Priority:** CRITICAL - everything depends on this.
77
78	#### Step 1.1: Environment and Config
79
80	| Config File | Setting | Value | Purpose |
81	|-------------|---------|-------|---------|
82	| `.env` | `DB_CONNECTION` | `sqlite` | Use SQLite as database |
83	| `.env` | `DB_DATABASE` | `database/database.sqlite` | SQLite database file path |
84	| `.env` | `CACHE_STORE` | `file` | File-based caching |
85	| `.env` | `SESSION_DRIVER` | `file` | File-based sessions |
86	| `.env` | `QUEUE_CONNECTION` | `sync` | Synchronous queue for dev |
87	| `.env` | `MAIL_MAILER` | `log` | Log-based mail for dev |
88	| `.env` | `FILESYSTEM_DISK` | `local` | Local filesystem storage |
89	| `config/database.php` | SQLite connection | WAL mode, foreign keys enabled, busy_timeout=5000, synchronous=normal | Optimized SQLite configuration |
90	| `config/session.php` | `driver` | `file` | Session driver |
91	| `config/session.php` | `lifetime` | `120` | Session lifetime in minutes |
92	| `config/cache.php` | `default` | `file` | Cache driver |
93	| `config/queue.php` | `default` | `sync` | Queue driver |
94	| `config/auth.php` | Guards | Add `customer` guard with `customers` provider | Customer storefront authentication |
95	| `config/auth.php` | Providers | Add `customers` provider pointing to Customer model | Customer user provider |
96	| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
97	| `config/filesystems.php` | `public` disk | Ensure configured for local storage | Media file storage |
98	| `config/logging.php` | Channels | Add structured JSON channel | Structured logging |
99
100	#### Step 1.2: Core Migrations (Batch 1-2)
101
102	Create in this dependency order:
103
104	1. `create_organizations_table` - no dependencies
105	2. `create_stores_table` - FK: `organization_id`
106	3. `create_store_domains_table` - FK: `store_id`
107	4. Modify default `users` migration - add `status`, `last_login_at`, `two_factor_*` columns; rename `password` to `password_hash` internally
108	5. `create_store_users_table` - composite PK: `store_id` + `user_id`, with `role` column
109	6. `create_store_settings_table` - PK: `store_id`
110
111	All monetary amounts stored as INTEGER in minor units (cents). All enums stored as TEXT with CHECK constraints. All timestamps as TEXT (ISO-8601) via Laravel.
112
113	#### Step 1.3: Core Models
114
115	**Files to create:**
116
117	| Type | Name | Purpose |
118	|------|------|---------|
119	| Model (with migration, factory, seeder) | `Organization` | Top-level tenant entity |
120	| Model (with migration, factory, seeder) | `Store` | Individual store within an organization |
121	| Model (with migration, factory, seeder) | `StoreDomain` | Domain mapping for a store |
122	| Model (pivot) | `StoreUser` | Pivot model with `role` attribute, custom Pivot class |
123	| Model (with migration, factory, seeder) | `StoreSettings` | Per-store configuration |
124
125	**Relationship definitions:**
126
127	| Model | Key Relationships |
128	|-------|-------------------|
129	| `Organization` | `hasMany(Store::class)` |
130	| `Store` | `belongsTo(Organization::class)`, `hasMany(StoreDomain::class)`, `belongsToMany(User::class)` through `store_users`, `hasOne(StoreSettings::class)` |
131	| `StoreDomain` | `belongsTo(Store::class)` |
132	| `User` | `belongsToMany(Store::class)` through `store_users` |
133	| `StoreUser` | Pivot model with `role` attribute, custom `Pivot` class |
134	| `StoreSettings` | `belongsTo(Store::class)` |
135
136	Each model must have:
137	- Explicit return type declarations on all methods
138	- Proper `$fillable` or `$guarded` arrays
139	- `casts()` method for JSON/enum columns
140	- Factory with meaningful default values
141	- Seeder that creates sample data
142
143	#### Step 1.4: Enums
144
145	**Files to create in `app/Enums/`:**
146
147	| Enum | Values |
148	|------|--------|
149	| `StoreStatus` | `Active`, `Suspended` |
150	| `StoreUserRole` | `Owner`, `Admin`, `Staff`, `Support` |
151	| `StoreDomainType` | `Storefront`, `Admin`, `Api` |
152
153	#### Step 1.5: Tenant Resolution Middleware
154
155	**File to create:** `App\Http\Middleware\ResolveStore`
156
157	Requirements:
158	1. Storefront routes: extract hostname from the request, look up `store_domains`, cache hostname-to-store_id for 5 minutes, bind `Store` as singleton via container instance `current_store`
159	2. Admin routes: read `current_store_id` from session, verify user has `store_users` record
160	3. Return 404 if hostname not found
161	4. Return 503 for suspended stores on storefront routes
162
163	**Registration in `bootstrap/app.php`:**
164	- Register middleware group `storefront` containing `ResolveStore`
165	- Register middleware group `admin` containing `ResolveStore`
166
167	#### Step 1.6: BelongsToStore Trait and Global Scope
168
169	**Files to create:**
170
171	| Type | Name | Purpose |
172	|------|------|---------|
173	| Trait | `App\Models\Concerns\BelongsToStore` | Applies StoreScope and auto-sets store_id on creating event |
174	| Scope | `App\Models\Scopes\StoreScope` | Applies `where('store_id', app('current_store')->id)` to all queries |
175
176	Applied on: products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
177
178	#### Step 1.7: Authentication
179
180	**Admin auth requirements:**
181
182	| Component | Description |
183	|-----------|-------------|
184	| Livewire component `Admin\Auth\Login` | Admin login form |
185	| Livewire component `Admin\Auth\Logout` | Admin logout action |
186	| Auth mechanism | Standard Laravel session auth via `Auth::guard('web')->attempt()` |
187	| Rate limiting | 5 attempts per minute per IP via `RateLimiter::for('login')` |
188	| Password reset | Standard Laravel flow with `Password::broker('users')` |
189
190	**Customer auth requirements:**
191
192	| Component | Description |
193	|-----------|-------------|
194	| Custom guard | `customer` guard with `customers` provider |
195	| Custom user provider | `CustomerUserProvider` that injects `store_id` into queries |
196	| Livewire component `Storefront\Account\Auth\Login` | Customer login form |
197	| Livewire component `Storefront\Account\Auth\Register` | Customer registration form |
198	| Uniqueness | Customer email unique per store, not globally |
199	| Rate limiting | 5 attempts per minute per IP |
200
201	**Rate limiter registration:**
202	- Register rate limiters in `AppServiceProvider::boot()` for login (5 per minute per IP)
203
204	#### Step 1.8: Authorization
205
206	**Files to create:**
207
208	| Type | Name | Purpose |
209	|------|------|---------|
210	| Policy | `ProductPolicy` | Product authorization based on store role |
211	| Policy | `OrderPolicy` | Order authorization based on store role |
212	| Policy | `CollectionPolicy` | Collection authorization based on store role |
213	| Policy | `DiscountPolicy` | Discount authorization based on store role |
214	| Policy | `CustomerPolicy` | Customer authorization based on store role |
215	| Policy | `StorePolicy` | Store authorization based on store role |
216	| Policy | `PagePolicy` | Page authorization based on store role |
217	| Policy | `ThemePolicy` | Theme authorization based on store role |
218	| Policy | `FulfillmentPolicy` | Fulfillment authorization based on store role |
219	| Policy | `RefundPolicy` | Refund authorization based on store role |
220
221	Each policy checks user role via `store_users` pivot for the current store. Helper on User model: `roleForStore(Store $store): ?StoreUserRole`. Register policies via auto-discovery or explicit binding in `AppServiceProvider`.
222
223	**Permission Matrix:**
224
225	| Permission | Owner | Admin | Staff | Support |
226	|------------|:-----:|:-----:|:-----:|:-------:|
227	| Manage store settings | Y | Y | N | N |
228	| Delete store | Y | N | N | N |
229	| Manage staff | Y | Y | N | N |
230	| Manage products (CRUD) | Y | Y | Y | N |
231	| Delete/archive products | Y | Y | N | N |
232	| Manage orders | Y | Y | Y | Read-only |
233	| Process refunds | Y | Y | N | N |
234	| Create fulfillments | Y | Y | Y | N |
235	| Manage discounts | Y | Y | Y | N |
236	| Manage themes | Y | Y | N | N |
237	| Manage apps | Y | Y | N | N |
238	| View analytics | Y | Y | Y | N |
239	| Manage shipping/tax | Y | Y | N | N |
240	| View customers | Y | Y | Y | Y |
241	| Update customers | Y | Y | Y | N |
242
243	---
244
245	### Phase 2: Catalog (Products, Variants, Inventory, Collections, Media)
246
247	**Priority:** HIGH - storefront and orders depend on this.
248
249	#### Step 2.1: Migrations (Batch 3-5)
250
251	Create in dependency order:
252
253	1. `create_products_table` - FK: `store_id`
254	2. `create_product_options_table` - FK: `product_id`
255	3. `create_product_option_values_table` - FK: `product_option_id`
256	4. `create_product_variants_table` - FK: `product_id`
257	5. `create_variant_option_values_table` - composite PK: `variant_id` + `product_option_value_id`
258	6. `create_inventory_items_table` - FK: `store_id`, `variant_id`
259	7. `create_collections_table` - FK: `store_id`
260	8. `create_collection_products_table` - composite PK: `collection_id` + `product_id`
261	9. `create_product_media_table` - FK: `product_id`
262
263	#### Step 2.2: Models with Relationships
264
265	**Files to create:**
266
267	| Type | Name | Purpose |
268	|------|------|---------|
269	| Model (with migration, factory, seeder) | `Product` | Product entity |
270	| Model (with migration, factory, seeder) | `ProductOption` | Product option (e.g., Size, Color) |
271	| Model (with migration, factory, seeder) | `ProductOptionValue` | Option values (e.g., S, M, L) |
272	| Model (with migration, factory, seeder) | `ProductVariant` | Product variant (SKU-level) |
273	| Model (with migration, factory, seeder) | `InventoryItem` | Inventory tracking per variant |
274	| Model (with migration, factory, seeder) | `Collection` | Product collection/category |
275	| Model (with migration, factory, seeder) | `ProductMedia` | Product images and videos |
276
277	**Relationship definitions:**
278
279	| Model | Key Relationships |
280	|-------|-------------------|
281	| `Product` | `hasMany(ProductVariant)`, `hasMany(ProductOption)`, `hasMany(ProductMedia)`, `belongsToMany(Collection)` via `collection_products` |
282	| `ProductOption` | `belongsTo(Product)`, `hasMany(ProductOptionValue)` |
283	| `ProductOptionValue` | `belongsTo(ProductOption)` |
284	| `ProductVariant` | `belongsTo(Product)`, `hasOne(InventoryItem)`, `belongsToMany(ProductOptionValue)` via `variant_option_values` |
285	| `InventoryItem` | `belongsTo(ProductVariant)` |
286	| `Collection` | `belongsToMany(Product)` via `collection_products` |
287	| `ProductMedia` | `belongsTo(Product)` |
288
289	**Enums to create:**
290
291	| Enum | Values |
292	|------|--------|
293	| `ProductStatus` | `Draft`, `Active`, `Archived` |
294	| `VariantStatus` | `Active`, `Archived` |
295	| `CollectionStatus` | `Draft`, `Active`, `Archived` |
296	| `MediaType` | `Image`, `Video` |
297	| `MediaStatus` | `Processing`, `Ready`, `Failed` |
298	| `InventoryPolicy` | `Deny`, `Continue` |
299
300	#### Step 2.3: Product Service
301
302	**File to create:** `App\Services\ProductService`
303
304	| Method | Description |
305	|--------|-------------|
306	| `create(Store $store, array $data): Product` | Creates product with nested variants/options |
307	| `update(Product $product, array $data): Product` | Updates product |
308	| `transitionStatus(Product $product, ProductStatus $newStatus): void` | Validates transitions per state machine |
309	| `delete(Product $product): void` | Hard delete only if draft with no order references; otherwise throws |
310
311	**File to create:** `App\Services\VariantMatrixService`
312
313	| Method | Description |
314	|--------|-------------|
315	| `rebuildMatrix(Product $product): void` | Computes cartesian product, creates missing variants, archives orphaned ones |
316
317	**File to create:** `App\Support\HandleGenerator`
318
319	| Method | Description |
320	|--------|-------------|
321	| `generate(string $title, string $table, int $storeId, ?int $excludeId = null): string` | Generates unique slugs |
322
323	#### Step 2.4: Inventory Service
324
325	**File to create:** `App\Services\InventoryService`
326
327	| Method | Description |
328	|--------|-------------|
329	| `checkAvailability(InventoryItem $item, int $quantity): bool` | Checks if sufficient available inventory exists |
330	| `reserve(InventoryItem $item, int $quantity): void` | Increments `quantity_reserved` |
331	| `release(InventoryItem $item, int $quantity): void` | Decrements `quantity_reserved` |
332	| `commit(InventoryItem $item, int $quantity): void` | Decrements both `on_hand` and `reserved` |
333	| `restock(InventoryItem $item, int $quantity): void` | Increments `quantity_on_hand` |
334
335	All operations wrapped in database transactions. Throws `InsufficientInventoryException` when policy is `deny` and available < quantity.
336
337	#### Step 2.5: Media Upload
338
339	**File to create:** `App\Jobs\ProcessMediaUpload`
340
341	Requirements:
342	- Resizes images to standard sizes (thumbnail 150x150, medium 600x600, large 1200x1200)
343	- Updates `ProductMedia` status from `processing` to `ready` (or `failed`)
344
345	Livewire file upload via `WithFileUploads` trait. Stored on local `public` disk.
346
347	---
348
349	### Phase 3: Themes, Pages, Navigation, Storefront Layout
350
351	**Priority:** HIGH - storefront rendering depends on this.
352
353	#### Step 3.1: Migrations (Batch 3)
354
355	1. `create_themes_table` - FK: `store_id`
356	2. `create_theme_files_table` - FK: `theme_id`
357	3. `create_theme_settings_table` - PK: `theme_id`
358	4. `create_pages_table` - FK: `store_id`
359	5. `create_navigation_menus_table` - FK: `store_id`
360	6. `create_navigation_items_table` - FK: `menu_id`
361
362	#### Step 3.2: Models
363
364	**Files to create:**
365
366	| Type | Name | Purpose |
367	|------|------|---------|
368	| Model (with migration, factory, seeder) | `Theme` | Store theme entity |
369	| Model (with migration, factory, seeder) | `ThemeFile` | Individual theme file |
370	| Model (with migration, factory, seeder) | `ThemeSettings` | Theme configuration |
371	| Model (with migration, factory, seeder) | `Page` | CMS page |
372	| Model (with migration, factory, seeder) | `NavigationMenu` | Navigation menu entity |
373	| Model (with migration, factory, seeder) | `NavigationItem` | Individual navigation item |
374
375	**Relationship definitions:**
376
377	| Model | Key Relationships |
378	|-------|-------------------|
379	| `Theme` | `belongsTo(Store)`, `hasMany(ThemeFile)`, `hasOne(ThemeSettings)` |
380	| `ThemeFile` | `belongsTo(Theme)` |
381	| `ThemeSettings` | `belongsTo(Theme)` |
382	| `Page` | `belongsTo(Store)` |
383	| `NavigationMenu` | `belongsTo(Store)`, `hasMany(NavigationItem)` |
384	| `NavigationItem` | `belongsTo(NavigationMenu)` |
385
386	**Enums to create:**
387
388	| Enum | Values |
389	|------|--------|
390	| `ThemeStatus` | `Draft`, `Published` |
391	| `PageStatus` | `Draft`, `Published`, `Archived` |
392	| `NavigationItemType` | `Link`, `Page`, `Collection`, `Product` |
393
394	#### Step 3.3: Storefront Blade Layout
395
396	**Directory structure for `resources/views/storefront/`:**
397
398	| Directory | Files |
399	|-----------|-------|
400	| `layouts/` | `app.blade.php` |

# 09 - Implementation Roadmap and Pest Test Specification

> E-commerce platform - Step-by-step build order and exhaustive test plan for Laravel 12 + Livewire v4 + Flux UI Free + SQLite + Tailwind CSS v4

---

## 1. Technology Decisions (Final)

These decisions adapt the original PDF spec for a self-contained, benchmarkable Laravel application. All decisions are final and must not be revisited.

| Concern | PDF Default | Our Decision | Rationale |
|---------|-------------|--------------|-----------|
| Database | PostgreSQL | SQLite | Self-contained, no external DB, zero config |
| Cache | Redis | File (Array for tests) | No Redis dependency |
| Session | Redis | File | No Redis dependency |
| Queue | Redis + Horizon | Sync (dev) / Database (prod) | No Redis dependency, sync for immediate feedback |
| Search | Elasticsearch + Scout | SQLite FTS5 | Self-contained, no external service |
| Storage | S3 | Local filesystem (storage/app/public) | No AWS dependency |
| Admin UI | React/Vue SPA | Livewire v4 + Flux UI Free | Server-rendered, matches project stack |
| Storefront | Blade SSR + Vite | Blade + Livewire + Tailwind v4 | Server-rendered, matches project stack |
| Auth (admin) | Sanctum SPA | Laravel session auth | Standard Livewire auth pattern |
| Auth (API) | Sanctum tokens | Sanctum personal access tokens | For programmatic API access |
| Auth (customer) | Separate guard | Custom `customer` guard | Storefront customer auth |
| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
| Payments | Stripe + PayPal | Mock PSP (in-process) | No external API calls. MockPaymentProvider handles credit card, PayPal, bank transfer with magic card numbers for testing. |
| Email | SMTP/SES | Log driver (dev) / SMTP | Log for benchmark, SMTP configurable |
| Monitoring | OpenTelemetry | Laravel Log (structured JSON) | Simplified for benchmark |
| CI/CD | GitHub Actions | Not implemented | Out of scope for one-shot benchmark |
| Docker | Multi-stage | Not implemented | App served by Laravel Herd locally |

---

## 2. Implementation Phases (Build Order)

Each phase builds on the previous. A coding agent should execute these in strict sequential order. Tests are written alongside each phase (see Section 3 for the full test specification).

### Phase Dependency Graph

```mermaid
flowchart TD
    P1[Phase 1: Foundation]
    P2[Phase 2: Catalog]
    P3[Phase 3: Themes and Storefront Layout]
    P4[Phase 4: Cart, Checkout, Discounts, Shipping, Taxes]
    P5[Phase 5: Payments, Orders, Fulfillment]
    P6[Phase 6: Customer Accounts]
    P7[Phase 7: Admin Panel]
    P8[Phase 8: Search]
    P9[Phase 9: Analytics]
    P10[Phase 10: Apps and Webhooks]
    P11[Phase 11: Polish]
    P12[Phase 12: Full Test Suite]

    P1 --> P2
    P1 --> P3
    P2 --> P4
    P3 --> P4
    P4 --> P5
    P5 --> P6
    P5 --> P7
    P2 --> P8
    P5 --> P9
    P5 --> P10
    P6 --> P11
    P7 --> P11
    P8 --> P11
    P9 --> P11
    P10 --> P11
    P11 --> P12
```

---

### Phase 1: Foundation (Migrations, Models, Middleware, Auth)

**Priority:** CRITICAL - everything depends on this.

#### Step 1.1: Environment and Config

| Config File | Setting | Value | Purpose |
|-------------|---------|-------|---------|
| `.env` | `DB_CONNECTION` | `sqlite` | Use SQLite as database |
| `.env` | `DB_DATABASE` | `database/database.sqlite` | SQLite database file path |
| `.env` | `CACHE_STORE` | `file` | File-based caching |
| `.env` | `SESSION_DRIVER` | `file` | File-based sessions |
| `.env` | `QUEUE_CONNECTION` | `sync` | Synchronous queue for dev |
| `.env` | `MAIL_MAILER` | `log` | Log-based mail for dev |
| `.env` | `FILESYSTEM_DISK` | `local` | Local filesystem storage |
| `config/database.php` | SQLite connection | WAL mode, foreign keys enabled, busy_timeout=5000, synchronous=normal | Optimized SQLite configuration |
| `config/session.php` | `driver` | `file` | Session driver |
| `config/session.php` | `lifetime` | `120` | Session lifetime in minutes |
| `config/cache.php` | `default` | `file` | Cache driver |
| `config/queue.php` | `default` | `sync` | Queue driver |
| `config/auth.php` | Guards | Add `customer` guard with `customers` provider | Customer storefront authentication |
| `config/auth.php` | Providers | Add `customers` provider pointing to Customer model | Customer user provider |
| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
| `config/filesystems.php` | `public` disk | Ensure configured for local storage | Media file storage |
| `config/logging.php` | Channels | Add structured JSON channel | Structured logging |

#### Step 1.2: Core Migrations (Batch 1-2)

Create in this dependency order:

1. `create_organizations_table` - no dependencies
2. `create_stores_table` - FK: `organization_id`
3. `create_store_domains_table` - FK: `store_id`
4. Modify default `users` migration - add `status`, `last_login_at`, `two_factor_*` columns; rename `password` to `password_hash` internally
5. `create_store_users_table` - composite PK: `store_id` + `user_id`, with `role` column
6. `create_store_settings_table` - PK: `store_id`

All monetary amounts stored as INTEGER in minor units (cents). All enums stored as TEXT with CHECK constraints. All timestamps as TEXT (ISO-8601) via Laravel.

#### Step 1.3: Core Models

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Organization` | Top-level tenant entity |
| Model (with migration, factory, seeder) | `Store` | Individual store within an organization |
| Model (with migration, factory, seeder) | `StoreDomain` | Domain mapping for a store |
| Model (pivot) | `StoreUser` | Pivot model with `role` attribute, custom Pivot class |
| Model (with migration, factory, seeder) | `StoreSettings` | Per-store configuration |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Organization` | `hasMany(Store::class)` |
| `Store` | `belongsTo(Organization::class)`, `hasMany(StoreDomain::class)`, `belongsToMany(User::class)` through `store_users`, `hasOne(StoreSettings::class)` |
| `StoreDomain` | `belongsTo(Store::class)` |
| `User` | `belongsToMany(Store::class)` through `store_users` |
| `StoreUser` | Pivot model with `role` attribute, custom `Pivot` class |
| `StoreSettings` | `belongsTo(Store::class)` |

Each model must have:
- Explicit return type declarations on all methods
- Proper `$fillable` or `$guarded` arrays
- `casts()` method for JSON/enum columns
- Factory with meaningful default values
- Seeder that creates sample data

#### Step 1.4: Enums

**Files to create in `app/Enums/`:**

| Enum | Values |
|------|--------|
| `StoreStatus` | `Active`, `Suspended` |
| `StoreUserRole` | `Owner`, `Admin`, `Staff`, `Support` |
| `StoreDomainType` | `Storefront`, `Admin`, `Api` |

#### Step 1.5: Tenant Resolution Middleware

**File to create:** `App\Http\Middleware\ResolveStore`

Requirements:
1. Storefront routes: extract hostname from the request, look up `store_domains`, cache hostname-to-store_id for 5 minutes, bind `Store` as singleton via container instance `current_store`
2. Admin routes: read `current_store_id` from session, verify user has `store_users` record
3. Return 404 if hostname not found
4. Return 503 for suspended stores on storefront routes

**Registration in `bootstrap/app.php`:**
- Register middleware group `storefront` containing `ResolveStore`
- Register middleware group `admin` containing `ResolveStore`

#### Step 1.6: BelongsToStore Trait and Global Scope

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Trait | `App\Models\Concerns\BelongsToStore` | Applies StoreScope and auto-sets store_id on creating event |
| Scope | `App\Models\Scopes\StoreScope` | Applies `where('store_id', app('current_store')->id)` to all queries |

Applied on: products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.

#### Step 1.7: Authentication

**Admin auth requirements:**

| Component | Description |
|-----------|-------------|
| Livewire component `Admin\Auth\Login` | Admin login form |
| Livewire component `Admin\Auth\Logout` | Admin logout action |
| Auth mechanism | Standard Laravel session auth via `Auth::guard('web')->attempt()` |
| Rate limiting | 5 attempts per minute per IP via `RateLimiter::for('login')` |
| Password reset | Standard Laravel flow with `Password::broker('users')` |

**Customer auth requirements:**

| Component | Description |
|-----------|-------------|
| Custom guard | `customer` guard with `customers` provider |
| Custom user provider | `CustomerUserProvider` that injects `store_id` into queries |
| Livewire component `Storefront\Account\Auth\Login` | Customer login form |
| Livewire component `Storefront\Account\Auth\Register` | Customer registration form |
| Uniqueness | Customer email unique per store, not globally |
| Rate limiting | 5 attempts per minute per IP |

**Rate limiter registration:**
- Register rate limiters in `AppServiceProvider::boot()` for login (5 per minute per IP)

#### Step 1.8: Authorization

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Policy | `ProductPolicy` | Product authorization based on store role |
| Policy | `OrderPolicy` | Order authorization based on store role |
| Policy | `CollectionPolicy` | Collection authorization based on store role |
| Policy | `DiscountPolicy` | Discount authorization based on store role |
| Policy | `CustomerPolicy` | Customer authorization based on store role |
| Policy | `StorePolicy` | Store authorization based on store role |
| Policy | `PagePolicy` | Page authorization based on store role |
| Policy | `ThemePolicy` | Theme authorization based on store role |
| Policy | `FulfillmentPolicy` | Fulfillment authorization based on store role |
| Policy | `RefundPolicy` | Refund authorization based on store role |

Each policy checks user role via `store_users` pivot for the current store. Helper on User model: `roleForStore(Store $store): ?StoreUserRole`. Register policies via auto-discovery or explicit binding in `AppServiceProvider`.

**Permission Matrix:**

| Permission | Owner | Admin | Staff | Support |
|------------|:-----:|:-----:|:-----:|:-------:|
| Manage store settings | Y | Y | N | N |
| Delete store | Y | N | N | N |
| Manage staff | Y | Y | N | N |
| Manage products (CRUD) | Y | Y | Y | N |
| Delete/archive products | Y | Y | N | N |
| Manage orders | Y | Y | Y | Read-only |
| Process refunds | Y | Y | N | N |
| Create fulfillments | Y | Y | Y | N |
| Manage discounts | Y | Y | Y | N |
| Manage themes | Y | Y | N | N |
| Manage apps | Y | Y | N | N |
| View analytics | Y | Y | Y | N |
| Manage shipping/tax | Y | Y | N | N |
| View customers | Y | Y | Y | Y |
| Update customers | Y | Y | Y | N |

---

### Phase 2: Catalog (Products, Variants, Inventory, Collections, Media)

**Priority:** HIGH - storefront and orders depend on this.

#### Step 2.1: Migrations (Batch 3-5)

Create in dependency order:

1. `create_products_table` - FK: `store_id`
2. `create_product_options_table` - FK: `product_id`
3. `create_product_option_values_table` - FK: `product_option_id`
4. `create_product_variants_table` - FK: `product_id`
5. `create_variant_option_values_table` - composite PK: `variant_id` + `product_option_value_id`
6. `create_inventory_items_table` - FK: `store_id`, `variant_id`
7. `create_collections_table` - FK: `store_id`
8. `create_collection_products_table` - composite PK: `collection_id` + `product_id`
9. `create_product_media_table` - FK: `product_id`

#### Step 2.2: Models with Relationships

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Product` | Product entity |
| Model (with migration, factory, seeder) | `ProductOption` | Product option (e.g., Size, Color) |
| Model (with migration, factory, seeder) | `ProductOptionValue` | Option values (e.g., S, M, L) |
| Model (with migration, factory, seeder) | `ProductVariant` | Product variant (SKU-level) |
| Model (with migration, factory, seeder) | `InventoryItem` | Inventory tracking per variant |
| Model (with migration, factory, seeder) | `Collection` | Product collection/category |
| Model (with migration, factory, seeder) | `ProductMedia` | Product images and videos |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Product` | `hasMany(ProductVariant)`, `hasMany(ProductOption)`, `hasMany(ProductMedia)`, `belongsToMany(Collection)` via `collection_products` |
| `ProductOption` | `belongsTo(Product)`, `hasMany(ProductOptionValue)` |
| `ProductOptionValue` | `belongsTo(ProductOption)` |
| `ProductVariant` | `belongsTo(Product)`, `hasOne(InventoryItem)`, `belongsToMany(ProductOptionValue)` via `variant_option_values` |
| `InventoryItem` | `belongsTo(ProductVariant)` |
| `Collection` | `belongsToMany(Product)` via `collection_products` |
| `ProductMedia` | `belongsTo(Product)` |

**Enums to create:**

| Enum | Values |
|------|--------|
| `ProductStatus` | `Draft`, `Active`, `Archived` |
| `VariantStatus` | `Active`, `Archived` |
| `CollectionStatus` | `Draft`, `Active`, `Archived` |
| `MediaType` | `Image`, `Video` |
| `MediaStatus` | `Processing`, `Ready`, `Failed` |
| `InventoryPolicy` | `Deny`, `Continue` |

#### Step 2.3: Product Service

**File to create:** `App\Services\ProductService`

| Method | Description |
|--------|-------------|
| `create(Store $store, array $data): Product` | Creates product with nested variants/options |
| `update(Product $product, array $data): Product` | Updates product |
| `transitionStatus(Product $product, ProductStatus $newStatus): void` | Validates transitions per state machine |
| `delete(Product $product): void` | Hard delete only if draft with no order references; otherwise throws |

**File to create:** `App\Services\VariantMatrixService`

| Method | Description |
|--------|-------------|
| `rebuildMatrix(Product $product): void` | Computes cartesian product, creates missing variants, archives orphaned ones |

**File to create:** `App\Support\HandleGenerator`

| Method | Description |
|--------|-------------|
| `generate(string $title, string $table, int $storeId, ?int $excludeId = null): string` | Generates unique slugs |

#### Step 2.4: Inventory Service

**File to create:** `App\Services\InventoryService`

| Method | Description |
|--------|-------------|
| `checkAvailability(InventoryItem $item, int $quantity): bool` | Checks if sufficient available inventory exists |
| `reserve(InventoryItem $item, int $quantity): void` | Increments `quantity_reserved` |
| `release(InventoryItem $item, int $quantity): void` | Decrements `quantity_reserved` |
| `commit(InventoryItem $item, int $quantity): void` | Decrements both `on_hand` and `reserved` |
| `restock(InventoryItem $item, int $quantity): void` | Increments `quantity_on_hand` |

All operations wrapped in database transactions. Throws `InsufficientInventoryException` when policy is `deny` and available < quantity.

#### Step 2.5: Media Upload

**File to create:** `App\Jobs\ProcessMediaUpload`

Requirements:
- Resizes images to standard sizes (thumbnail 150x150, medium 600x600, large 1200x1200)
- Updates `ProductMedia` status from `processing` to `ready` (or `failed`)

Livewire file upload via `WithFileUploads` trait. Stored on local `public` disk.

---

### Phase 3: Themes, Pages, Navigation, Storefront Layout

**Priority:** HIGH - storefront rendering depends on this.

#### Step 3.1: Migrations (Batch 3)

1. `create_themes_table` - FK: `store_id`
2. `create_theme_files_table` - FK: `theme_id`
3. `create_theme_settings_table` - PK: `theme_id`
4. `create_pages_table` - FK: `store_id`
5. `create_navigation_menus_table` - FK: `store_id`
6. `create_navigation_items_table` - FK: `menu_id`

#### Step 3.2: Models

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Theme` | Store theme entity |
| Model (with migration, factory, seeder) | `ThemeFile` | Individual theme file |
| Model (with migration, factory, seeder) | `ThemeSettings` | Theme configuration |
| Model (with migration, factory, seeder) | `Page` | CMS page |
| Model (with migration, factory, seeder) | `NavigationMenu` | Navigation menu entity |
| Model (with migration, factory, seeder) | `NavigationItem` | Individual navigation item |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Theme` | `belongsTo(Store)`, `hasMany(ThemeFile)`, `hasOne(ThemeSettings)` |
| `ThemeFile` | `belongsTo(Theme)` |
| `ThemeSettings` | `belongsTo(Theme)` |
| `Page` | `belongsTo(Store)` |
| `NavigationMenu` | `belongsTo(Store)`, `hasMany(NavigationItem)` |
| `NavigationItem` | `belongsTo(NavigationMenu)` |

**Enums to create:**

| Enum | Values |
|------|--------|
| `ThemeStatus` | `Draft`, `Published` |
| `PageStatus` | `Draft`, `Published`, `Archived` |
| `NavigationItemType` | `Link`, `Page`, `Collection`, `Product` |

#### Step 3.3: Storefront Blade Layout

**Directory structure for `resources/views/storefront/`:**

| Directory | Files |
|-----------|-------|
| `layouts/` | `app.blade.php` |