diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..af12857
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+node_modules/
+dist/
+build/
+*.log
+.env*
+.DS_Store
+Thumbs.db
+*.tmp
+*.swp
+.vscode/
+.idea/
+coverage/
+*.tsbuildinfo
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..2bd5a0a
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+22
diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md
index a4670ff..c064026 100644
--- a/.specify/memory/constitution.md
+++ b/.specify/memory/constitution.md
@@ -1,50 +1,145 @@
-# [PROJECT_NAME] Constitution
-
+
+# Encounter Console Constitution
## Core Principles
-### [PRINCIPLE_1_NAME]
-
-[PRINCIPLE_1_DESCRIPTION]
-
+### I. Deterministic Domain Core
-### [PRINCIPLE_2_NAME]
-
-[PRINCIPLE_2_DESCRIPTION]
-
+All domain logic MUST be implemented as pure state transitions.
+Given the same input state and action, the output state MUST be
+identical across runs. Side effects (I/O, randomness, clocks) MUST
+be injected at the boundary, never sourced inside the domain layer.
-### [PRINCIPLE_3_NAME]
-
-[PRINCIPLE_3_DESCRIPTION]
-
+- State transitions MUST be expressed as pure functions.
+- Random values (e.g., dice rolls) MUST be passed in as resolved
+ inputs, not generated within the domain.
+- Domain functions MUST NOT depend on wall-clock time, network, or
+ filesystem.
-### [PRINCIPLE_4_NAME]
-
-[PRINCIPLE_4_DESCRIPTION]
-
+### II. Layered Architecture
-### [PRINCIPLE_5_NAME]
-
-[PRINCIPLE_5_DESCRIPTION]
-
+The codebase MUST be organized into four layers with strict
+dependency direction:
-## [SECTION_2_NAME]
-
+1. **Domain** — pure types, state transitions, validation rules.
+ No imports from other layers.
+2. **Application** — orchestrates domain operations, manages
+ workflows, coordinates use cases. Application defines port
+ interfaces that Adapters implement. May import Domain only.
+3. **Adapters** — I/O, persistence, UI rendering, external APIs.
+ May import Application and Domain.
+4. **Agent** — AI-assisted features (suggestions, analysis).
+ May import Application and Domain as read-only consumers.
-[SECTION_2_CONTENT]
-
+A module in an inner layer MUST NOT import from an outer layer.
-## [SECTION_3_NAME]
-
+### III. Agent Boundary
-[SECTION_3_CONTENT]
-
+The agent layer MAY read domain events and current state. The agent
+MAY produce suggestions, annotations, or recommendations. The agent
+MUST NOT mutate domain state directly. All agent-originated changes
+MUST flow through the Application layer as explicit user-confirmed
+commands.
+
+- Agent output MUST be clearly labeled as suggestions.
+- No silent or automatic application of agent recommendations.
+
+### IV. Clarification-First
+
+Before making any non-trivial assumption during specification,
+planning, or implementation, the agent MUST surface a clarification
+question to the user. "Non-trivial" means any decision that would
+alter observable behavior, data model shape, or public API surface.
+The agent MUST also ask when multiple valid interpretations exist,
+when a choice would affect architectural layering, or when scope
+would expand beyond the current spec. The agent MUST NOT silently
+choose among valid alternatives.
+
+### V. Escalation Gates
+
+Any feature, requirement, or scope change not present in the current
+spec MUST be rejected at implementation time until the spec is
+explicitly updated. The workflow is:
+
+1. Identify the out-of-scope item.
+2. Document it as a proposal.
+3. Update the spec via `/speckit.specify` or `/speckit.clarify`.
+4. Only then proceed with implementation.
+
+### VI. MVP Baseline Language
+
+Constraints in this constitution and in specs MUST use MVP baseline
+language ("MVP baseline does not include X") rather than permanent
+architectural bans ("X is forbidden"). This preserves the option to
+add capabilities in future iterations without constitutional
+amendment. The current MVP baseline is local-first and single-user;
+this is a starting scope, not a permanent restriction.
+
+### VII. No Gameplay Rules in Constitution
+
+This constitution MUST NOT contain concrete gameplay mechanics,
+rule-system specifics, or encounter resolution logic. Such details
+belong in feature specs. The constitution governs process,
+architecture, and quality — not product behavior.
+
+## Scope Constraints
+
+- The Encounter Console's primary focus is initiative tracking and
+ encounter state management. Adjacent capabilities (e.g., richer
+ game-engine features) are not in the MVP baseline but may be
+ added via spec updates in future iterations.
+- Technology choices, UI framework, and storage mechanism are
+ spec-level decisions, not constitutional mandates.
+- Testing strategy (unit, integration, contract) is determined per
+ feature spec; the constitution requires only that domain logic
+ remains testable via pure-function assertions.
+
+## Development Workflow
+
+- No change may be merged unless all automated checks (tests and
+ static analysis as defined by the project) pass.
+- Every feature begins with a spec (`/speckit.specify`).
+- Implementation follows the plan → tasks → implement pipeline.
+- Domain logic MUST be testable without mocks for external systems.
+- Long-running or multi-step state transitions SHOULD be verifiable
+ through reproducible event logs or snapshot-style tests.
+- Commits SHOULD be atomic and map to individual tasks where
+ practical.
+- Layer boundary compliance MUST be verified by automated import
+ rules or architectural tests. Agent-assisted or manual review MAY
+ supplement but not replace automated checks.
## Governance
-
-[GOVERNANCE_RULES]
-
+This constitution is the highest-authority document for the
+Encounter Console project. All specs, plans, and implementations
+MUST comply with its principles.
-**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
-
+**Amendment procedure**:
+1. Propose change with rationale.
+2. Update constitution via `/speckit.constitution`.
+3. Verify downstream template compatibility (Sync Impact Report).
+4. A human maintainer MUST review and commit the update before
+ ratification takes effect.
+5. Commit with version bump.
+
+**Versioning policy**: MAJOR.MINOR.PATCH per semantic versioning.
+- MAJOR: principle removal or backward-incompatible redefinition.
+- MINOR: new principle or materially expanded guidance.
+- PATCH: clarification, typo, or non-semantic refinement.
+
+**Compliance review**: Every spec and plan MUST include a
+Constitution Check section validating adherence to all principles.
+
+**Version**: 1.0.3 | **Ratified**: 2026-03-03 | **Last Amended**: 2026-03-03
diff --git a/apps/web/index.html b/apps/web/index.html
new file mode 100644
index 0000000..90a2dce
--- /dev/null
+++ b/apps/web/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Initiative Tracker
+
+
+
+
+
+
diff --git a/apps/web/package.json b/apps/web/package.json
new file mode 100644
index 0000000..a1bd7d9
--- /dev/null
+++ b/apps/web/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "web",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc --build && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@initiative/application": "workspace:*",
+ "@initiative/domain": "workspace:*",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^4.3.0",
+ "vite": "^6.2.0"
+ }
+}
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
new file mode 100644
index 0000000..4781b57
--- /dev/null
+++ b/apps/web/src/App.tsx
@@ -0,0 +1,3 @@
+export function App() {
+ return Initiative Tracker
;
+}
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
new file mode 100644
index 0000000..1aa0ba5
--- /dev/null
+++ b/apps/web/src/main.tsx
@@ -0,0 +1,12 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { App } from "./App";
+
+const root = document.getElementById("root");
+if (root) {
+ createRoot(root).render(
+
+
+ ,
+ );
+}
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
new file mode 100644
index 0000000..370eb9c
--- /dev/null
+++ b/apps/web/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "rootDir": "src",
+ "outDir": "dist",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "references": [
+ { "path": "../../packages/domain" },
+ { "path": "../../packages/application" }
+ ]
+}
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
new file mode 100644
index 0000000..58bd0a9
--- /dev/null
+++ b/apps/web/vite.config.ts
@@ -0,0 +1,6 @@
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..f5a69dd
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,33 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
+ "files": {
+ "includes": [
+ "**",
+ "!**/dist/**",
+ "!.claude/**",
+ "!.specify/**",
+ "!specs/**"
+ ]
+ },
+ "assist": {
+ "enabled": true,
+ "actions": {
+ "source": {
+ "organizeImports": {
+ "level": "on"
+ }
+ }
+ }
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "tab",
+ "lineWidth": 80
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..15facff
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "private": true,
+ "packageManager": "pnpm@10.6.0",
+ "devDependencies": {
+ "@biomejs/biome": "2.0.0",
+ "typescript": "^5.8.0",
+ "vitest": "^3.0.0"
+ },
+ "scripts": {
+ "format": "biome format --write .",
+ "format:check": "biome format .",
+ "lint": "biome lint .",
+ "lint:fix": "biome lint --write .",
+ "typecheck": "tsc --build",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "check": "biome check . && tsc --build && vitest run"
+ }
+}
diff --git a/packages/application/package.json b/packages/application/package.json
new file mode 100644
index 0000000..c3a58d6
--- /dev/null
+++ b/packages/application/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@initiative/application",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "dependencies": {
+ "@initiative/domain": "workspace:*"
+ }
+}
diff --git a/packages/application/src/index.ts b/packages/application/src/index.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/packages/application/src/index.ts
@@ -0,0 +1 @@
+export {};
diff --git a/packages/application/tsconfig.json b/packages/application/tsconfig.json
new file mode 100644
index 0000000..c1de3b1
--- /dev/null
+++ b/packages/application/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "references": [{ "path": "../domain" }]
+}
diff --git a/packages/domain/package.json b/packages/domain/package.json
new file mode 100644
index 0000000..2ec35c4
--- /dev/null
+++ b/packages/domain/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@initiative/domain",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts"
+}
diff --git a/packages/domain/src/__tests__/layer-boundaries.test.ts b/packages/domain/src/__tests__/layer-boundaries.test.ts
new file mode 100644
index 0000000..8f78ea0
--- /dev/null
+++ b/packages/domain/src/__tests__/layer-boundaries.test.ts
@@ -0,0 +1,16 @@
+import { describe, expect, it } from "vitest";
+import { checkLayerBoundaries } from "../../../../scripts/check-layer-boundaries.mjs";
+
+describe("layer boundaries", () => {
+ it("domain and application packages have no forbidden imports", () => {
+ const violations = checkLayerBoundaries();
+ if (violations.length > 0) {
+ const messages = violations.map(
+ (v) =>
+ `${v.file}:${v.line} imports "${v.importPath}" (forbidden: ${v.forbidden})`,
+ );
+ throw new Error(`Layer boundary violations:\n ${messages.join("\n ")}`);
+ }
+ expect(violations).toHaveLength(0);
+ });
+});
diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/packages/domain/src/index.ts
@@ -0,0 +1 @@
+export {};
diff --git a/packages/domain/tsconfig.json b/packages/domain/tsconfig.json
new file mode 100644
index 0000000..6a77bfd
--- /dev/null
+++ b/packages/domain/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["src/__tests__"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..ce76670
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,1550 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@biomejs/biome':
+ specifier: 2.0.0
+ version: 2.0.0
+ typescript:
+ specifier: ^5.8.0
+ version: 5.9.3
+ vitest:
+ specifier: ^3.0.0
+ version: 3.2.4
+
+ apps/web:
+ dependencies:
+ '@initiative/application':
+ specifier: workspace:*
+ version: link:../../packages/application
+ '@initiative/domain':
+ specifier: workspace:*
+ version: link:../../packages/domain
+ react:
+ specifier: ^19.0.0
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.4(react@19.2.4)
+ devDependencies:
+ '@types/react':
+ specifier: ^19.0.0
+ version: 19.2.14
+ '@types/react-dom':
+ specifier: ^19.0.0
+ version: 19.2.3(@types/react@19.2.14)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.0
+ version: 4.7.0(vite@6.4.1)
+ vite:
+ specifier: ^6.2.0
+ version: 6.4.1
+
+ packages/application:
+ dependencies:
+ '@initiative/domain':
+ specifier: workspace:*
+ version: link:../domain
+
+ packages/domain: {}
+
+packages:
+
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.0':
+ resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.0':
+ resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.6':
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.0':
+ resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.0':
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ engines: {node: '>=6.9.0'}
+
+ '@biomejs/biome@2.0.0':
+ resolution: {integrity: sha512-BlUoXEOI/UQTDEj/pVfnkMo8SrZw3oOWBDrXYFT43V7HTkIUDkBRY53IC5Jx1QkZbaB+0ai1wJIfYwp9+qaJTQ==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@2.0.0':
+ resolution: {integrity: sha512-QvqWYtFFhhxdf8jMAdJzXW+Frc7X8XsnHQLY+TBM1fnT1TfeV/v9vsFI5L2J7GH6qN1+QEEJ19jHibCY2Ypplw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@2.0.0':
+ resolution: {integrity: sha512-5JFhls1EfmuIH4QGFPlNpxJQFC6ic3X1ltcoLN+eSRRIPr6H/lUS1ttuD0Fj7rPgPhZqopK/jfH8UVj/1hIsQw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@2.0.0':
+ resolution: {integrity: sha512-Bxsz8ki8+b3PytMnS5SgrGV+mbAWwIxI3ydChb/d1rURlJTMdxTTq5LTebUnlsUWAX6OvJuFeiVq9Gjn1YbCyA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@2.0.0':
+ resolution: {integrity: sha512-BAH4QVi06TzAbVchXdJPsL0Z/P87jOfes15rI+p3EX9/EGTfIjaQ9lBVlHunxcmoptaA5y1Hdb9UYojIhmnjIw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@2.0.0':
+ resolution: {integrity: sha512-tiQ0ABxMJb9I6GlfNp0ulrTiQSFacJRJO8245FFwE3ty3bfsfxlU/miblzDIi+qNrgGsLq5wIZcVYGp4c+HXZA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@2.0.0':
+ resolution: {integrity: sha512-09PcOGYTtkopWRm6mZ/B6Mr6UHdkniUgIG/jLBv+2J8Z61ezRE+xQmpi3yNgUrFIAU4lPA9atg7mhvE/5Bo7Wg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@2.0.0':
+ resolution: {integrity: sha512-vrTtuGu91xNTEQ5ZcMJBZuDlqr32DWU1r14UfePIGndF//s2WUAmer4FmgoPgruo76rprk37e8S2A2c0psXdxw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@2.0.0':
+ resolution: {integrity: sha512-2USVQ0hklNsph/KIR72ZdeptyXNnQ3JdzPn3NbjI4Sna34CnxeiYAaZcZzXPDl5PYNFBivV4xmvT3Z3rTmyDBg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+ '@rollup/rollup-android-arm-eabi@4.59.0':
+ resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.59.0':
+ resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.59.0':
+ resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.59.0':
+ resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.59.0':
+ resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.59.0':
+ resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.59.0':
+ resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.59.0':
+ resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.59.0':
+ resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.59.0':
+ resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.59.0':
+ resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.59.0':
+ resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.59.0':
+ resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.59.0':
+ resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.59.0':
+ resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.59.0':
+ resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.59.0':
+ resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.59.0':
+ resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.59.0':
+ resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.59.0':
+ resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.59.0':
+ resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.59.0':
+ resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.59.0':
+ resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.59.0':
+ resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.59.0':
+ resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.14':
+ resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ '@vitest/expect@3.2.4':
+ resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
+
+ '@vitest/mocker@3.2.4':
+ resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@3.2.4':
+ resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
+
+ '@vitest/runner@3.2.4':
+ resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
+
+ '@vitest/snapshot@3.2.4':
+ resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
+
+ '@vitest/spy@3.2.4':
+ resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
+
+ '@vitest/utils@3.2.4':
+ resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ baseline-browser-mapping@2.10.0:
+ resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ caniuse-lite@1.0.30001776:
+ resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==}
+
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
+ check-error@2.1.3:
+ resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
+ engines: {node: '>= 16'}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
+ electron-to-chromium@1.5.307:
+ resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ loupe@3.2.1:
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pathval@2.0.1:
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.8:
+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@19.2.4:
+ resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
+ peerDependencies:
+ react: ^19.2.4
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react@19.2.4:
+ resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
+ engines: {node: '>=0.10.0'}
+
+ rollup@4.59.0:
+ resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ strip-literal@3.1.0:
+ resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinypool@1.1.1:
+ resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@4.0.4:
+ resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
+ engines: {node: '>=14.0.0'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ vite-node@3.2.4:
+ resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
+ vite@6.4.1:
+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@3.2.4:
+ resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.4
+ '@vitest/ui': 3.2.4
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/debug':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+snapshots:
+
+ '@babel/code-frame@7.29.0':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.0': {}
+
+ '@babel/core@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.29.0
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+
+ '@babel/parser@7.29.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.0':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@biomejs/biome@2.0.0':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 2.0.0
+ '@biomejs/cli-darwin-x64': 2.0.0
+ '@biomejs/cli-linux-arm64': 2.0.0
+ '@biomejs/cli-linux-arm64-musl': 2.0.0
+ '@biomejs/cli-linux-x64': 2.0.0
+ '@biomejs/cli-linux-x64-musl': 2.0.0
+ '@biomejs/cli-win32-arm64': 2.0.0
+ '@biomejs/cli-win32-x64': 2.0.0
+
+ '@biomejs/cli-darwin-arm64@2.0.0':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@2.0.0':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@2.0.0':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@2.0.0':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@2.0.0':
+ optional: true
+
+ '@biomejs/cli-linux-x64@2.0.0':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@2.0.0':
+ optional: true
+
+ '@biomejs/cli-win32-x64@2.0.0':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/rollup-android-arm-eabi@4.59.0':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.59.0':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.59.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.59.0':
+ optional: true
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/react-dom@19.2.3(@types/react@19.2.14)':
+ dependencies:
+ '@types/react': 19.2.14
+
+ '@types/react@19.2.14':
+ dependencies:
+ csstype: 3.2.3
+
+ '@vitejs/plugin-react@4.7.0(vite@6.4.1)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitest/expect@3.2.4':
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.2.4(vite@6.4.1)':
+ dependencies:
+ '@vitest/spy': 3.2.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 6.4.1
+
+ '@vitest/pretty-format@3.2.4':
+ dependencies:
+ tinyrainbow: 2.0.0
+
+ '@vitest/runner@3.2.4':
+ dependencies:
+ '@vitest/utils': 3.2.4
+ pathe: 2.0.3
+ strip-literal: 3.1.0
+
+ '@vitest/snapshot@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@3.2.4':
+ dependencies:
+ tinyspy: 4.0.4
+
+ '@vitest/utils@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ loupe: 3.2.1
+ tinyrainbow: 2.0.0
+
+ assertion-error@2.0.1: {}
+
+ baseline-browser-mapping@2.10.0: {}
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.10.0
+ caniuse-lite: 1.0.30001776
+ electron-to-chromium: 1.5.307
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ cac@6.7.14: {}
+
+ caniuse-lite@1.0.30001776: {}
+
+ chai@5.3.3:
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.3
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
+ check-error@2.1.3: {}
+
+ convert-source-map@2.0.0: {}
+
+ csstype@3.2.3: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ deep-eql@5.0.2: {}
+
+ electron-to-chromium@1.5.307: {}
+
+ es-module-lexer@1.7.0: {}
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ escalade@3.2.0: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ expect-type@1.3.0: {}
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ js-tokens@4.0.0: {}
+
+ js-tokens@9.0.1: {}
+
+ jsesc@3.1.0: {}
+
+ json5@2.2.3: {}
+
+ loupe@3.2.1: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.27: {}
+
+ pathe@2.0.3: {}
+
+ pathval@2.0.1: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ postcss@8.5.8:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@19.2.4(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ scheduler: 0.27.0
+
+ react-refresh@0.17.0: {}
+
+ react@19.2.4: {}
+
+ rollup@4.59.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.59.0
+ '@rollup/rollup-android-arm64': 4.59.0
+ '@rollup/rollup-darwin-arm64': 4.59.0
+ '@rollup/rollup-darwin-x64': 4.59.0
+ '@rollup/rollup-freebsd-arm64': 4.59.0
+ '@rollup/rollup-freebsd-x64': 4.59.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.59.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.59.0
+ '@rollup/rollup-linux-arm64-gnu': 4.59.0
+ '@rollup/rollup-linux-arm64-musl': 4.59.0
+ '@rollup/rollup-linux-loong64-gnu': 4.59.0
+ '@rollup/rollup-linux-loong64-musl': 4.59.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.59.0
+ '@rollup/rollup-linux-ppc64-musl': 4.59.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.59.0
+ '@rollup/rollup-linux-riscv64-musl': 4.59.0
+ '@rollup/rollup-linux-s390x-gnu': 4.59.0
+ '@rollup/rollup-linux-x64-gnu': 4.59.0
+ '@rollup/rollup-linux-x64-musl': 4.59.0
+ '@rollup/rollup-openbsd-x64': 4.59.0
+ '@rollup/rollup-openharmony-arm64': 4.59.0
+ '@rollup/rollup-win32-arm64-msvc': 4.59.0
+ '@rollup/rollup-win32-ia32-msvc': 4.59.0
+ '@rollup/rollup-win32-x64-gnu': 4.59.0
+ '@rollup/rollup-win32-x64-msvc': 4.59.0
+ fsevents: 2.3.3
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ siginfo@2.0.0: {}
+
+ source-map-js@1.2.1: {}
+
+ stackback@0.0.2: {}
+
+ std-env@3.10.0: {}
+
+ strip-literal@3.1.0:
+ dependencies:
+ js-tokens: 9.0.1
+
+ tinybench@2.9.0: {}
+
+ tinyexec@0.3.2: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tinypool@1.1.1: {}
+
+ tinyrainbow@2.0.0: {}
+
+ tinyspy@4.0.4: {}
+
+ typescript@5.9.3: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ vite-node@3.2.4:
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.4.1
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vite@6.4.1:
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.8
+ rollup: 4.59.0
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ vitest@3.2.4:
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/expect': 3.2.4
+ '@vitest/mocker': 3.2.4(vite@6.4.1)
+ '@vitest/pretty-format': 3.2.4
+ '@vitest/runner': 3.2.4
+ '@vitest/snapshot': 3.2.4
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ debug: 4.4.3
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.15
+ tinypool: 1.1.1
+ tinyrainbow: 2.0.0
+ vite: 6.4.1
+ vite-node: 3.2.4
+ why-is-node-running: 2.3.0
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ yallist@3.1.1: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..0e5a073
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - "packages/*"
+ - "apps/*"
diff --git a/scripts/check-layer-boundaries.mjs b/scripts/check-layer-boundaries.mjs
new file mode 100644
index 0000000..3a8193c
--- /dev/null
+++ b/scripts/check-layer-boundaries.mjs
@@ -0,0 +1,109 @@
+import { readdirSync, readFileSync } from "node:fs";
+import { join, relative } from "node:path";
+
+const ROOT = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
+
+const FORBIDDEN = {
+ "packages/domain/src": [
+ "@initiative/application",
+ "apps/",
+ "react",
+ "react-dom",
+ "vite",
+ ],
+ "packages/application/src": ["apps/", "react", "react-dom", "vite"],
+};
+
+/** Directories to skip when collecting files */
+const SKIP_DIRS = new Set(["__tests__", "node_modules"]);
+
+/** @param {string} dir */
+function collectTsFiles(dir) {
+ /** @type {string[]} */
+ const results = [];
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
+ const full = join(dir, entry.name);
+ if (entry.isDirectory()) {
+ if (!SKIP_DIRS.has(entry.name)) {
+ results.push(...collectTsFiles(full));
+ }
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
+ results.push(full);
+ }
+ }
+ return results;
+}
+
+/**
+ * Check if an import path matches a forbidden module.
+ * "vite" should match "vite" and "vite/foo" but not "vitest".
+ * @param {string} importPath
+ * @param {string} forbidden
+ */
+function matchesForbidden(importPath, forbidden) {
+ if (forbidden.endsWith("/")) {
+ return importPath.startsWith(forbidden);
+ }
+ return importPath === forbidden || importPath.startsWith(`${forbidden}/`);
+}
+
+/** @returns {{ file: string, line: number, importPath: string, forbidden: string }[]} */
+export function checkLayerBoundaries() {
+ /** @type {{ file: string, line: number, importPath: string, forbidden: string }[]} */
+ const violations = [];
+
+ for (const [srcDir, forbidden] of Object.entries(FORBIDDEN)) {
+ const absDir = join(ROOT, srcDir);
+ let files;
+ try {
+ files = collectTsFiles(absDir);
+ } catch {
+ continue;
+ }
+
+ for (const file of files) {
+ const content = readFileSync(file, "utf-8");
+ const lines = content.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const match = line.match(
+ /(?:import|from)\s+["']([^"']+)["']|require\s*\(\s*["']([^"']+)["']\s*\)/,
+ );
+ if (!match) continue;
+ const importPath = match[1] || match[2];
+ for (const f of forbidden) {
+ if (matchesForbidden(importPath, f)) {
+ violations.push({
+ file: relative(ROOT, file),
+ line: i + 1,
+ importPath,
+ forbidden: f,
+ });
+ }
+ }
+ }
+ }
+ }
+
+ return violations;
+}
+
+// Run as CLI if invoked directly
+if (
+ process.argv[1] &&
+ (process.argv[1].endsWith("check-layer-boundaries.mjs") ||
+ process.argv[1] === new URL(import.meta.url).pathname)
+) {
+ const violations = checkLayerBoundaries();
+ if (violations.length > 0) {
+ console.error("Layer boundary violations found:");
+ for (const v of violations) {
+ console.error(
+ ` ${v.file}:${v.line} — imports "${v.importPath}" (forbidden: ${v.forbidden})`,
+ );
+ }
+ process.exit(1);
+ } else {
+ console.log("No layer boundary violations found.");
+ }
+}
diff --git a/specs/001-advance-turn/plan.md b/specs/001-advance-turn/plan.md
new file mode 100644
index 0000000..49f6c12
--- /dev/null
+++ b/specs/001-advance-turn/plan.md
@@ -0,0 +1,349 @@
+# Implementation Plan: Advance Turn
+
+**Branch**: `001-advance-turn` | **Date**: 2026-03-03 | **Spec**: [spec.md](./spec.md)
+**Input**: Feature specification from `/specs/001-advance-turn/spec.md`
+
+## Summary
+
+Implement the AdvanceTurn domain operation as a pure function that
+transitions an Encounter to the next combatant, wrapping rounds and
+emitting TurnAdvanced / RoundAdvanced domain events. Stand up the
+pnpm monorepo skeleton, Biome tooling, and Vitest test harness so
+that all constitution merge-gate requirements are satisfied from the
+first commit.
+
+## Technical Context
+
+**Node**: 22 LTS (pinned via `.nvmrc`)
+**Language/Version**: TypeScript 5.8 (strict mode)
+**Primary Dependencies**: React 19 (pin to major; minor upgrades
+allowed), Vite 6.2
+**Storage**: In-memory only (MVP baseline)
+**Testing**: Vitest 3.0
+**Lint/Format**: Biome 2.0.0 (exact version, single tool — no
+Prettier, no ESLint)
+**Package Manager**: pnpm 10.6 (pinned via `packageManager` field
+in root `package.json`)
+**Target Platform**: Static web app (modern browsers)
+**Project Type**: Monorepo — library packages + web app
+**Performance Goals**: N/A (walking skeleton)
+**Constraints**: Domain package must have zero React/Vite imports
+**Scale/Scope**: Single feature, ~5 source files, ~1 test file
+
+## Constitution Check
+
+| Principle | Status | Notes |
+|-----------|--------|-------|
+| I. Deterministic Domain Core | PASS | `advanceTurn` is a pure function; no I/O, randomness, or clocks |
+| II. Layered Architecture | PASS | `packages/domain` → `packages/application` → `apps/web`; strict dependency direction enforced by automated import check |
+| III. Agent Boundary | N/A | Agent layer out of scope for this feature |
+| IV. Clarification-First | PASS | No ambiguous decisions remain; spec fully clarified |
+| V. Escalation Gates | PASS | Scope strictly limited to spec; out-of-scope items listed |
+| VI. MVP Baseline Language | PASS | No permanent bans; "MVP baseline does not include" used |
+| VII. No Gameplay Rules | PASS | No gameplay mechanics in plan or constitution |
+| Merge Gate | PASS | `pnpm check` script runs format, lint, typecheck, test |
+
+## Project Structure
+
+### Documentation (this feature)
+
+```text
+specs/001-advance-turn/
+├── spec.md
+└── plan.md
+```
+
+### Source Code (repository root)
+
+```text
+packages/
+├── domain/
+│ ├── package.json
+│ ├── tsconfig.json
+│ └── src/
+│ ├── types.ts # CombatantId, Combatant, Encounter
+│ ├── events.ts # TurnAdvanced, RoundAdvanced, DomainEvent
+│ ├── advance-turn.ts # advanceTurn pure function
+│ └── index.ts # public barrel export
+├── application/
+│ ├── package.json
+│ ├── tsconfig.json
+│ └── src/
+│ ├── ports.ts # EncounterStore port interface
+│ ├── advance-turn-use-case.ts
+│ └── index.ts
+apps/
+└── web/
+ ├── package.json
+ ├── tsconfig.json
+ ├── vite.config.ts
+ ├── index.html
+ └── src/
+ ├── main.tsx
+ ├── App.tsx
+ └── hooks/
+ └── use-encounter.ts
+
+# Testing (co-located with domain package)
+packages/domain/
+└── src/
+ └── __tests__/
+ └── advance-turn.test.ts
+
+# Root config
+├── .nvmrc # pins Node 22
+├── pnpm-workspace.yaml
+├── biome.json
+├── tsconfig.base.json
+└── package.json # packageManager field pins pnpm; root scripts
+```
+
+**Structure Decision**: pnpm workspace monorepo with two packages
+(`domain`, `application`) and one app (`web`). Domain is
+framework-agnostic TypeScript. Application imports domain only.
+Web app (React + Vite) imports both.
+
+## Tooling & Merge Gate
+
+### Scripts (root package.json)
+
+```jsonc
+{
+ "scripts": {
+ "format": "biome format --write .",
+ "format:check": "biome format .",
+ "lint": "biome lint .",
+ "lint:fix": "biome lint --write .",
+ "typecheck": "tsc --build",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "check": "biome check . && tsc --build && vitest run"
+ }
+}
+```
+
+`pnpm check` is the single merge gate: format + lint + typecheck +
+test. The layer boundary check runs as a Vitest test (see below),
+so it executes as part of `vitest run` — no separate script needed.
+
+### Layer Boundary Enforcement
+
+Biome does not natively support cross-package import restrictions.
+A lightweight `scripts/check-layer-boundaries.mjs` script will:
+
+1. Scan `packages/domain/src/**/*.ts` — assert zero imports from
+ `@initiative/application`, `apps/`, `react`, `vite`.
+2. Scan `packages/application/src/**/*.ts` — assert zero imports
+ from `apps/`, `react`, `vite`.
+3. Exit non-zero on violation with a clear error message.
+
+This script is invoked by a Vitest test
+(`packages/domain/src/__tests__/layer-boundaries.test.ts`) so it
+runs automatically as part of `vitest run` inside `pnpm check`.
+No separate `check:layer` script is needed — the layer boundary
+check is guaranteed to execute on every merge-gate run.
+
+### Biome Configuration (biome.json)
+
+```jsonc
+{
+ "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
+ "organizeImports": { "enabled": true },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "tab",
+ "lineWidth": 80
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ }
+ }
+}
+```
+
+### TypeScript Configuration
+
+- `tsconfig.base.json` at root: strict mode, composite projects,
+ path aliases (`@initiative/domain`, `@initiative/application`).
+- Each package extends `tsconfig.base.json` with its own
+ `include`/`references`.
+- `apps/web/tsconfig.json` references both packages.
+
+## Milestones
+
+### Milestone 1: Tooling & Domain (walking skeleton)
+
+Stand up monorepo, Biome, Vitest, TypeScript project references,
+and implement the complete AdvanceTurn domain logic with all tests.
+
+**Exit criteria**: `pnpm check` passes. All 8 acceptance scenarios
+green. Layer boundary check green. No React or Vite dependencies in
+`packages/domain` or `packages/application`.
+
+### Milestone 2: Application + Minimal Web Shell
+
+Wire up the application use case and a minimal React UI that
+displays the encounter state and has a single "Next Turn" button.
+
+**Exit criteria**: `pnpm check` passes. Clicking "Next Turn" in the
+browser advances the turn with correct round wrapping. The app
+builds with `vite build`.
+
+## Task List
+
+### Phase 1: Setup (Milestone 1)
+
+- [X] **T001** Initialize pnpm workspace and root config
+ - Create `pnpm-workspace.yaml` listing `packages/*` and `apps/*`
+ - Create `.nvmrc` pinning Node 22
+ - Create root `package.json` with `packageManager` field pinning
+ pnpm 10.6 and scripts (check, test, lint, format, typecheck)
+ - Create `biome.json` at root
+ - Create `tsconfig.base.json` (strict, composite, path aliases)
+ - **Acceptance**: `pnpm install` succeeds; `biome check .` runs
+ without config errors
+
+- [X] **T002** [P] Create `packages/domain` package skeleton
+ - `package.json` (name: `@initiative/domain`, no dependencies)
+ - `tsconfig.json` extending base, composite: true
+ - Empty `src/index.ts`
+ - **Acceptance**: `tsc --build packages/domain` succeeds
+
+- [X] **T003** [P] Create `packages/application` package skeleton
+ - `package.json` (name: `@initiative/application`,
+ depends on `@initiative/domain`)
+ - `tsconfig.json` extending base, references domain
+ - Empty `src/index.ts`
+ - **Acceptance**: `tsc --build packages/application` succeeds
+
+- [X] **T004** [P] Create `apps/web` package skeleton
+ - `package.json` with React, Vite, depends on both packages
+ - `tsconfig.json` referencing both packages
+ - `vite.config.ts` (minimal)
+ - `index.html` + `src/main.tsx` + `src/App.tsx` (placeholder)
+ - **Acceptance**: `pnpm --filter web dev` starts; `vite build`
+ succeeds
+
+- [X] **T005** Configure Vitest
+ - Add `vitest` as root dev dependency
+ - Create `vitest.config.ts` at root (workspace mode) or per
+ package as needed
+ - Verify `pnpm test` runs (0 tests, exits clean)
+ - **Acceptance**: `pnpm test` exits 0
+
+- [X] **T006** Create layer boundary check script
+ - `scripts/check-layer-boundaries.mjs`: scans domain and
+ application source for forbidden imports
+ - `packages/domain/src/__tests__/layer-boundaries.test.ts`:
+ wraps the script as a Vitest test
+ - **Acceptance**: test passes on clean skeleton; fails if a
+ forbidden import is manually added (verify, then remove)
+
+### Phase 2: Domain Implementation (Milestone 1)
+
+- [ ] **T007** Define domain types in `packages/domain/src/types.ts`
+ - `CombatantId` (branded string or opaque type)
+ - `Combatant` (carries a CombatantId)
+ - `Encounter` (combatants array, activeIndex, roundNumber)
+ - Factory function `createEncounter` that validates INV-1, INV-2,
+ INV-3
+ - **Acceptance**: types compile; `createEncounter([])` returns
+ error; `createEncounter([a])` returns valid Encounter
+
+- [ ] **T008** [P] Define domain events in
+ `packages/domain/src/events.ts`
+ - `TurnAdvanced { previousCombatantId, newCombatantId,
+ roundNumber }`
+ - `RoundAdvanced { newRoundNumber }`
+ - `DomainEvent = TurnAdvanced | RoundAdvanced`
+ - **Acceptance**: types compile; events are plain data (no
+ classes with methods)
+
+- [ ] **T009** Implement `advanceTurn` in
+ `packages/domain/src/advance-turn.ts`
+ - Signature: `(encounter: Encounter) =>
+ { encounter: Encounter; events: DomainEvent[] } | DomainError`
+ - Implements FR-001 through FR-005
+ - Returns error for empty combatant list (INV-1)
+ - Emits TurnAdvanced on every call (INV-5)
+ - Emits TurnAdvanced then RoundAdvanced on wrap (event order
+ contract)
+ - **Acceptance**: compiles; satisfies type contract
+
+- [ ] **T010** Write tests for all 8 acceptance scenarios +
+ invariants in
+ `packages/domain/src/__tests__/advance-turn.test.ts`
+ - Scenarios 1–8 from spec (Given/When/Then)
+ - INV-1: empty encounter rejected
+ - INV-2: activeIndex always in bounds (property check across
+ scenarios)
+ - INV-3: roundNumber never decreases
+ - INV-4: determinism — same input produces same output (call
+ twice, assert deep equal)
+ - INV-5: every success emits at least TurnAdvanced
+ - Event ordering: on wrap, events array is
+ [TurnAdvanced, RoundAdvanced] in that order
+ - **Acceptance**: `pnpm test` — all tests green; `pnpm check` —
+ full pipeline green
+
+- [ ] **T011** Export public API from `packages/domain/src/index.ts`
+ - Re-export types, events, `advanceTurn`, `createEncounter`
+ - **Acceptance**: consuming packages can
+ `import { advanceTurn } from "@initiative/domain"`
+
+**Milestone 1 checkpoint**: `pnpm check` passes (format + lint +
+typecheck + test + layer boundaries). All 8 scenarios + invariants
+green.
+
+### Phase 3: Application + Web Shell (Milestone 2)
+
+- [ ] **T012** Define port interface in
+ `packages/application/src/ports.ts`
+ - `EncounterStore` port: `get(): Encounter`, `save(e: Encounter)`
+ - **Acceptance**: compiles; no imports from adapters or React
+
+- [ ] **T013** Implement `AdvanceTurnUseCase` in
+ `packages/application/src/advance-turn-use-case.ts`
+ - Accepts `EncounterStore` port
+ - Calls `advanceTurn` from domain, saves result, returns events
+ - **Acceptance**: compiles; imports only from `@initiative/domain`
+ and local ports
+
+- [ ] **T014** Export public API from
+ `packages/application/src/index.ts`
+ - Re-export use case and port types
+ - **Acceptance**: consuming app can import from
+ `@initiative/application`
+
+- [ ] **T015** Implement `useEncounter` hook in
+ `apps/web/src/hooks/use-encounter.ts`
+ - In-memory implementation of `EncounterStore` port (React state)
+ - Exposes current encounter state + `advanceTurn` action
+ - Initializes with a hardcoded 3-combatant encounter for demo
+ - **Acceptance**: hook compiles; usable in a React component
+
+- [ ] **T016** Wire up `App.tsx`
+ - Display: current combatant name, round number, combatant list
+ with active indicator
+ - Single "Next Turn" button calling the use case
+ - Display emitted events (optional, for demo clarity)
+ - **Acceptance**: `vite build` succeeds; clicking "Next Turn"
+ cycles through combatants and increments rounds correctly
+
+**Milestone 2 checkpoint**: `pnpm check` passes. App runs in
+browser. Full round-trip from button click → domain pure function →
+UI update verified manually.
+
+## Risks & Open Questions
+
+| # | Item | Severity | Mitigation |
+|---|------|----------|------------|
+| 1 | pnpm workspace + TypeScript project references can have path resolution quirks with Vite | Low | Use `vite-tsconfig-paths` plugin if needed; test early in T004 |
+| 2 | Biome config format may change across versions | Low | Pinned to exact 2.0.0; `$schema` in config validates structure |
+| 3 | Layer boundary script is a lightweight grep — not a full architectural fitness function | Low | Sufficient for walking skeleton; can upgrade to a Biome plugin or `dependency-cruiser` later if needed |
+
+## Complexity Tracking
+
+No constitution violations. No complexity justifications needed.
diff --git a/specs/001-advance-turn/spec.md b/specs/001-advance-turn/spec.md
new file mode 100644
index 0000000..e0ef176
--- /dev/null
+++ b/specs/001-advance-turn/spec.md
@@ -0,0 +1,160 @@
+# Feature Specification: Advance Turn
+
+**Feature Branch**: `001-advance-turn`
+**Created**: 2026-03-03
+**Status**: Draft
+**Input**: Walking-skeleton domain feature — deterministic turn advancement
+
+## User Scenarios & Testing *(mandatory)*
+
+### User Story 1 - Advance Turn (Priority: P1)
+
+A game master running an encounter advances the turn to the next
+combatant in initiative order. When the last combatant in the round
+finishes, the round number increments and play wraps to the first
+combatant.
+
+**Why this priority**: This is the irreducible core of an initiative
+tracker. Without turn advancement, no other feature has meaning.
+
+**Independent Test**: Can be fully tested as a pure state transition
+with no I/O, persistence, or UI. Given an Encounter value and an
+AdvanceTurn action, assert the resulting Encounter value and emitted
+domain events.
+
+**Acceptance Scenarios**:
+
+1. **Given** an encounter with combatants [A, B, C], activeIndex 0,
+ roundNumber 1,
+ **When** AdvanceTurn,
+ **Then** activeIndex is 1, roundNumber is 1,
+ and a TurnAdvanced event is emitted with
+ previousCombatantId A, newCombatantId B, roundNumber 1.
+
+2. **Given** an encounter with combatants [A, B, C], activeIndex 1,
+ roundNumber 1,
+ **When** AdvanceTurn,
+ **Then** activeIndex is 2, roundNumber is 1,
+ and a TurnAdvanced event is emitted with
+ previousCombatantId B, newCombatantId C, roundNumber 1.
+
+3. **Given** an encounter with combatants [A, B, C], activeIndex 2,
+ roundNumber 1,
+ **When** AdvanceTurn,
+ **Then** activeIndex is 0, roundNumber is 2,
+ and events are emitted in order: TurnAdvanced
+ (previousCombatantId C, newCombatantId A, roundNumber 2) then
+ RoundAdvanced (newRoundNumber 2).
+
+4. **Given** an encounter with combatants [A, B, C], activeIndex 2,
+ roundNumber 5,
+ **When** AdvanceTurn,
+ **Then** activeIndex is 0, roundNumber is 6,
+ and events are emitted in order: TurnAdvanced then RoundAdvanced
+ (verifies round increment is not hardcoded to 2).
+
+5. **Given** an encounter with a single combatant [A], activeIndex 0,
+ roundNumber 1,
+ **When** AdvanceTurn,
+ **Then** activeIndex is 0, roundNumber is 2,
+ and events are emitted in order: TurnAdvanced
+ (previousCombatantId A, newCombatantId A, roundNumber 2) then
+ RoundAdvanced (newRoundNumber 2).
+
+6. **Given** an encounter with combatants [A, B], activeIndex 0,
+ roundNumber 1,
+ **When** AdvanceTurn is applied twice in sequence,
+ **Then** after the first: activeIndex 1, roundNumber 1;
+ after the second: activeIndex 0, roundNumber 2.
+
+7. **Given** an encounter with an empty combatant list,
+ **When** AdvanceTurn,
+ **Then** the operation MUST fail with an invalid-encounter error.
+ No events are emitted. State is unchanged.
+
+8. **Given** an encounter with combatants [A, B, C], activeIndex 0,
+ roundNumber 1,
+ **When** AdvanceTurn is applied three times,
+ **Then** the encounter completes a full round cycle:
+ activeIndex returns to 0 and roundNumber is 2.
+
+---
+
+### Edge Cases
+
+- Empty combatant list: AdvanceTurn MUST reject with an error.
+- Single combatant: every advance wraps and increments the round.
+- Large round numbers: no overflow or special-case behavior; round
+ increments uniformly.
+
+## Domain Model *(mandatory)*
+
+### Key Entities
+
+- **Combatant**: An identified participant in the encounter. For this
+ feature, a combatant is an opaque identity (e.g., a name or id).
+ The MVP baseline does not include HP, conditions, or stats.
+- **Encounter**: The aggregate root. Contains an ordered list of
+ combatants (pre-sorted by initiative), an activeIndex pointing to
+ the current combatant, and a roundNumber (positive integer,
+ starting at 1).
+
+### Domain Events
+
+- **TurnAdvanced**: Emitted on every successful AdvanceTurn.
+ Carries: previousCombatantId, newCombatantId, roundNumber.
+- **RoundAdvanced**: Emitted when activeIndex wraps past the last
+ combatant. Carries: newRoundNumber.
+
+When a round boundary is crossed, both TurnAdvanced and
+RoundAdvanced MUST be emitted in that order (TurnAdvanced first).
+This emission order is part of the observable domain contract and
+MUST be verified by tests.
+
+### Invariants
+
+- **INV-1**: An encounter MUST have at least one combatant.
+ Operations on an empty encounter MUST fail.
+- **INV-2**: activeIndex MUST always satisfy
+ 0 <= activeIndex < len(combatants).
+- **INV-3**: roundNumber MUST be a positive integer (>= 1) and MUST
+ only increase (never decrease or reset).
+- **INV-4**: AdvanceTurn MUST be a pure function of the current
+ encounter state. Given identical input, output MUST be identical.
+- **INV-5**: Every successful AdvanceTurn MUST emit at least one
+ domain event (TurnAdvanced). No silent state changes.
+
+## Requirements *(mandatory)*
+
+### Functional Requirements
+
+- **FR-001**: The domain MUST expose an AdvanceTurn operation that
+ accepts an Encounter and returns the next Encounter state plus
+ emitted domain events.
+- **FR-002**: AdvanceTurn MUST increment activeIndex by 1, wrapping
+ to 0 when past the last combatant.
+- **FR-003**: When activeIndex wraps to 0, roundNumber MUST
+ increment by 1.
+- **FR-004**: AdvanceTurn on an empty encounter MUST return an error
+ without modifying state or emitting events.
+- **FR-005**: Domain events MUST be returned as values from the
+ operation, not dispatched via side effects.
+
+### Out of Scope (MVP baseline does not include)
+
+- Initiative rolling or combatant ordering logic
+- Hit points, damage, conditions, or status effects
+- Adding or removing combatants mid-encounter
+- Persistence, serialization, or storage
+- UI, CLI, or any adapter layer
+- Agent behavior or suggestions
+
+## Success Criteria *(mandatory)*
+
+### Measurable Outcomes
+
+- **SC-001**: All 8 acceptance scenarios pass as deterministic,
+ pure-function tests with no I/O dependencies.
+- **SC-002**: Invariants INV-1 through INV-5 are verified by tests.
+- **SC-003**: The domain module has zero imports from application,
+ adapter, or agent layers (layer boundary compliance).
diff --git a/specs/001-advance-turn/tasks.md b/specs/001-advance-turn/tasks.md
new file mode 100644
index 0000000..2f65b5d
--- /dev/null
+++ b/specs/001-advance-turn/tasks.md
@@ -0,0 +1,128 @@
+# Tasks: Advance Turn
+
+**Input**: Design documents from `/specs/001-advance-turn/`
+**Prerequisites**: plan.md (required), spec.md (required)
+
+**Organization**: Tasks follow the phased structure from plan.md. There is only one user story (US1 — Advance Turn, P1), so phases map directly to the plan's milestones.
+
+## Format: `[ID] [P?] [Story?] Description`
+
+- **[P]**: Can run in parallel (different files, no dependencies)
+- **[US1]**: User Story 1 — Advance Turn
+- Exact file paths included in every task
+
+---
+
+## Phase 1: Setup (Milestone 1 — Tooling)
+
+**Purpose**: Initialize pnpm monorepo, Biome, TypeScript, Vitest, and layer boundary enforcement
+
+- [X] T001 Initialize pnpm workspace and root config — create `pnpm-workspace.yaml`, `.nvmrc` (Node 22), root `package.json` (with `packageManager` pinning pnpm 10.6 and scripts: check, test, lint, format, typecheck), `biome.json`, and `tsconfig.base.json` (strict, composite, path aliases)
+- [X] T002 [P] Create `packages/domain` package skeleton — `packages/domain/package.json` (`@initiative/domain`, no deps), `packages/domain/tsconfig.json` (extends base, composite), empty `packages/domain/src/index.ts`
+- [X] T003 [P] Create `packages/application` package skeleton — `packages/application/package.json` (`@initiative/application`, depends on `@initiative/domain`), `packages/application/tsconfig.json` (extends base, references domain), empty `packages/application/src/index.ts`
+- [X] T004 [P] Create `apps/web` package skeleton — `apps/web/package.json` (React 19, Vite 6.2, depends on both packages), `apps/web/tsconfig.json`, `apps/web/vite.config.ts`, `apps/web/index.html`, `apps/web/src/main.tsx`, `apps/web/src/App.tsx` (placeholder)
+- [X] T005 Configure Vitest — add `vitest` as root dev dependency, create `vitest.config.ts` at root (workspace mode or per-package), verify `pnpm test` exits 0
+- [X] T006 Create layer boundary check — `scripts/check-layer-boundaries.mjs` (scans domain/application for forbidden imports) and `packages/domain/src/__tests__/layer-boundaries.test.ts` (wraps script as Vitest test)
+
+**Checkpoint**: `pnpm install` succeeds, `biome check .` runs, `tsc --build` compiles, `pnpm test` exits 0 with layer boundary test green.
+
+---
+
+## Phase 2: Domain Implementation — User Story 1: Advance Turn (Priority: P1) (Milestone 1)
+
+**Goal**: Implement the complete AdvanceTurn domain logic as a pure function with all 8 acceptance scenarios and invariant tests.
+
+**Independent Test**: Pure state transition — given an Encounter value and AdvanceTurn action, assert resulting Encounter and emitted domain events. No I/O, persistence, or UI needed.
+
+- [ ] T007 [US1] Define domain types in `packages/domain/src/types.ts` — `CombatantId` (branded/opaque), `Combatant`, `Encounter` (combatants, activeIndex, roundNumber), factory `createEncounter` enforcing INV-1, INV-2, INV-3
+- [ ] T008 [P] [US1] Define domain events in `packages/domain/src/events.ts` — `TurnAdvanced`, `RoundAdvanced`, `DomainEvent` union (plain data, no classes)
+- [ ] T009 [US1] Implement `advanceTurn` in `packages/domain/src/advance-turn.ts` — pure function `(Encounter) => { encounter, events } | DomainError`, implements FR-001 through FR-005
+- [ ] T010 [US1] Write tests for all 8 acceptance scenarios + invariants in `packages/domain/src/__tests__/advance-turn.test.ts` — scenarios 1–8, INV-1 through INV-5, event ordering on round wrap
+- [ ] T011 [US1] Export public API from `packages/domain/src/index.ts` — re-export types, events, `advanceTurn`, `createEncounter`
+
+**Checkpoint (Milestone 1)**: `pnpm check` passes (format + lint + typecheck + test + layer boundaries). All 8 scenarios + invariants green. No React/Vite imports in domain or application.
+
+---
+
+## Phase 3: Application + Web Shell (Milestone 2)
+
+**Goal**: Wire up the application use case and minimal React UI with a "Next Turn" button.
+
+- [ ] T012 Define port interface in `packages/application/src/ports.ts` — `EncounterStore` port: `get(): Encounter`, `save(e: Encounter)`
+- [ ] T013 Implement `AdvanceTurnUseCase` in `packages/application/src/advance-turn-use-case.ts` — accepts `EncounterStore`, calls `advanceTurn`, saves result, returns events
+- [ ] T014 Export public API from `packages/application/src/index.ts` — re-export use case and port types
+- [ ] T015 Implement `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — in-memory `EncounterStore` via React state, exposes encounter state + `advanceTurn` action, hardcoded 3-combatant demo
+- [ ] T016 Wire up `apps/web/src/App.tsx` — display current combatant, round number, combatant list with active indicator, "Next Turn" button, emitted events
+
+**Checkpoint (Milestone 2)**: `pnpm check` passes. `vite build` succeeds. Clicking "Next Turn" cycles combatants and increments rounds correctly.
+
+---
+
+## Dependencies & Execution Order
+
+### Phase Dependencies
+
+- **Phase 1 (Setup)**: No dependencies — start immediately
+- **Phase 2 (Domain)**: Depends on Phase 1 completion
+- **Phase 3 (App + Web)**: Depends on Phase 2 completion (needs domain types and `advanceTurn`)
+
+### Within Phase 1
+
+- T001 must complete first (workspace and root config)
+- T002, T003, T004 can run in parallel [P] after T001
+- T005 depends on T001 (needs root package.json)
+- T006 depends on T002 and T005 (needs domain package + Vitest)
+
+### Within Phase 2
+
+- T007 must complete first (types needed by everything)
+- T008 can run in parallel [P] with T007 (events are independent types)
+- T009 depends on T007 and T008 (uses types and events)
+- T010 depends on T009 (tests the implementation)
+- T011 depends on T007, T008, T009 (exports all public API)
+
+### Within Phase 3
+
+- T012 first (port interface)
+- T013 depends on T012 (uses port)
+- T014 depends on T013 (exports use case)
+- T015 depends on T014 (uses application layer)
+- T016 depends on T015 (uses hook)
+
+---
+
+## Parallel Opportunities
+
+```text
+# After T001 completes:
+T002, T003, T004 — all package skeletons in parallel
+
+# After T007 starts:
+T008 — domain events can be written in parallel with types
+
+# Independent stories: only one user story (US1), so parallelism is within-phase only
+```
+
+---
+
+## Implementation Strategy
+
+### MVP First (Milestone 1)
+
+1. Complete Phase 1: Setup (T001–T006)
+2. Complete Phase 2: Domain (T007–T011)
+3. **STOP and VALIDATE**: `pnpm check` passes, all 8 scenarios green
+
+### Full Feature (Milestone 2)
+
+4. Complete Phase 3: App + Web Shell (T012–T016)
+5. **VALIDATE**: `pnpm check` passes, app runs in browser
+
+---
+
+## Notes
+
+- All task IDs (T001–T016) match plan.md — no scope expansion
+- Single user story (US1: Advance Turn) — no cross-story dependencies
+- Tests (T010) are included as specified in plan.md and spec.md
+- Domain package must have zero React/Vite imports (enforced by T006)
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..ceb7f35
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "composite": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+ "verbatimModuleSyntax": true
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1e9dca2
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "files": [],
+ "references": [
+ { "path": "packages/domain" },
+ { "path": "packages/application" },
+ { "path": "apps/web" }
+ ]
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..07a81fe
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ include: ["packages/*/src/**/*.test.ts"],
+ passWithNoTests: true,
+ },
+});