Files
josh 1e99356b27 docs(01-01): complete scaffold and test infra plan
Wave 1 of Phase 1 complete. Phaser 4 + React 19 + Vite 8 + TypeScript 6
scaffold builds (npm run build green); 15 Phase-1 deps installed at
locked versions; 7 architectural-firewall directories ready under src/;
repo-root /content/ + /assets/ trees ready; Vitest (happy-dom) +
Playwright wired with passing sentinel; package.json scripts
pre-declared for the entire Phase 1 plan-set so Wave 2 can run in
parallel without colliding on package.json.

3 deviations auto-fixed (1 blocking, 2 missing-critical):
1. Built scaffold by hand because @phaserjs/create-game v1.3.2 is
   interactive-only — plan's documented fallback path was used.
2. build script wraps tsc -b before vite build so strict-TS gates
   every build (CLAUDE.md Code Style invariant).
3. Added *.tsbuildinfo to .gitignore (TS 6 incremental cache files).

Wave 2 readiness: Plan 02 must use ESLint 9 flat-config format
(eslint.config.js); legacy .eslintrc.* not supported. fake-indexeddb
pre-installed for Plan 03 IDB tests. inkjs + inklecate installed but
no .ink files compiled (Phase 2 PIPE-02 owns that).
2026-05-08 23:23:15 -04:00

292 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 01-foundations-and-doctrine
plan: 01
subsystem: infra
tags: [phaser, react, vite, typescript, vitest, playwright, scaffold, firewall]
# Dependency graph
requires:
- phase: 00
provides: Project planning artifacts (.planning/PROJECT.md, REQUIREMENTS.md, ROADMAP.md, CLAUDE.md, Phase-1 RESEARCH+CONTEXT)
provides:
- Buildable Phaser 4 + React 19 + Vite 8 + TypeScript 6 scaffold (npm run build green)
- Seven architectural-firewall directories under src/ with .gitkeep markers (sim, render, ui, save, content, audio, store)
- Repo-root /content/ tree (with /dialogue/ and /seasons/) and /assets/ tree
- All Phase-1 dependencies installed at versions verified in RESEARCH.md (15+ packages)
- Pre-declared package.json scripts the entire Phase 1 plan-set will rely on (dev, build, preview, lint, test, test:watch, validate:assets, compile:ink, ci)
- Vitest config (happy-dom env, passWithNoTests:false) and Playwright config (testDir: tests/e2e, no specs yet)
- Sentinel test proving the Vitest runner is wired and happy-dom is active
affects: [01-02-firewall-and-lint, 01-03-save-layer, 01-04-content-pipeline, 01-05-asset-provenance, 01-06-doctrine-docs, 01-07-ci-workflow, 02-onwards]
# Tech tracking
tech-stack:
added:
- phaser@^4.1.0 (game framework, locked per CLAUDE.md)
- react@^19.2.6 + react-dom@^19.2.6 (UI shell)
- vite@^8.0.11 + @vitejs/plugin-react@^6.0.1 (dev server + bundler)
- typescript@^6.0.3 (strict mode enabled)
- idb@^8.0.3 (Plan 03 IDB wrapper)
- lz-string@^1.5.0 (Plan 03 save compression + Base64)
- zod@^4.4.3 (Plan 03/04 schema validation)
- crc-32@^1.2.2 (Plan 03 save checksum)
- gray-matter@^4.0.3 (Plan 04 .md frontmatter parsing)
- yaml@^2.8.4 (Plan 04 .yaml content)
- inkjs@^2.4.0 (Plan 04 / Phase 2 Ink runtime — installed only)
- vitest@^4.1.5 + @vitest/ui (Phase 1 sentinel; Plans 03/04 add real tests)
- happy-dom@^20.9.0 (Vitest DOM env for window/IDB tests)
- fake-indexeddb@^6.2.5 (Plan 03 IDB tests — pre-installed here so Plan 03 doesn't re-edit package.json)
- @playwright/test@^1.59.1 (installed only; first spec lands Phase 2 PIPE-07)
- eslint@^9 + eslint-plugin-boundaries@^6.0.2 (Plan 02 firewall lint)
- inklecate@^1.8.1 (Phase 2 Ink compiler — installed only)
- @types/react@^19, @types/react-dom@^19, @types/node@^22 (TS types)
patterns:
- "Architectural firewall directories declared as siblings: src/{sim,render,ui,save,content,audio,store}/ — siblings to template-provided src/game/. Plan 02's ESLint boundaries rule will lint imports between these."
- "Repo-root /content/ and /assets/ trees (CONTEXT D-11, D-12) — single package, no monorepo, no workspaces. Solo-dev scope."
- "Pre-declared script keys in package.json so Wave-2 plans (0206) running in parallel never collide on package.json."
- "TypeScript strict mode enforced via tsconfig.json + tsconfig.app.json + tsconfig.node.json (referenced project layout)."
- "Test infrastructure shape: Vitest with happy-dom env (DOM + window) for unit/sim/save tests; Playwright with webServer reuse for e2e smoke (Phase 2+). passWithNoTests:false enforces 'green CI ⇒ tests ran' (RESEARCH CI Pitfall B)."
key-files:
created:
- package.json (Phase-1 deps + 9 pre-declared scripts)
- package-lock.json (lockfile committed for reproducible installs)
- .gitignore (node_modules, dist, coverage, *.tsbuildinfo, etc.)
- index.html (Vite entry)
- vite.config.ts (React plugin + dev server config)
- tsconfig.json + tsconfig.app.json + tsconfig.node.json (strict TS, referenced projects)
- src/main.tsx (React root mount)
- src/App.tsx (mounts <PhaserGame />)
- src/PhaserGame.tsx (forwardRef wrapper around Phaser.Game lifecycle)
- src/game/main.ts (minimal Phaser config + StartGame factory)
- src/game/scenes/Boot.ts (Phase 1 placeholder Phaser scene)
- src/vite-env.d.ts (vite/client types reference)
- src/sim/.gitkeep + src/render/.gitkeep + src/ui/.gitkeep + src/save/.gitkeep + src/content/.gitkeep + src/audio/.gitkeep + src/store/.gitkeep (firewall markers)
- content/.gitkeep + content/dialogue/.gitkeep + content/seasons/.gitkeep + assets/.gitkeep (authored-content + AI-asset trees)
- vitest.config.ts (happy-dom env, passWithNoTests:false)
- playwright.config.ts (testDir tests/e2e, webServer wiring)
- src/__sentinel__.test.ts (proves Vitest runner + happy-dom env)
modified: []
key-decisions:
- "Built the equivalent React + Vite + TypeScript scaffold by hand because the official `npm create @phaserjs/game@latest` scaffolder (create-game v1.3.2) is interactive-only — the documented `--template react-ts --yes` flags are silently ignored. The plan's Step 1 explicitly authorizes this fallback ('If the official scaffolder cannot run non-interactively, fall back to manually constructing the equivalent file tree')."
- "Adopted the referenced-projects tsconfig pattern (root tsconfig.json with `references` to tsconfig.app.json and tsconfig.node.json) — current Vite + TS 6 best practice. Strict mode enforced at all three layers."
- "build script set to `tsc -b && vite build` (not bare `vite build`) so the plan's TypeScript-strict invariant is verified on every build, not silently bypassed."
- "*.tsbuildinfo added to .gitignore — tsc -b emits these incremental-build cache files; they are dev artifacts, not source."
- "Sentinel test asserts both `1+1===2` AND `typeof globalThis.window === 'object'` so it doubles as proof that happy-dom is active (Plan 03 will rely on this env)."
- "Pre-installed `fake-indexeddb@^6` here in Plan 01 (rather than waiting for Plan 03) so Plan 03 Task 2 can `import 'fake-indexeddb/auto'` without re-editing package.json — defends Wave-2 parallel execution."
patterns-established:
- "Scaffold-from-scratch fallback: when an upstream scaffolder cannot run non-interactively, manually construct the equivalent file tree using the published template-shape as the spec. Lock dependency versions in package.json from the published template's package.json (or RESEARCH.md when newer). Future plans should follow the same fallback discipline."
- "Pre-declare-all-scripts pattern: Wave-N plans that will run in parallel must not collide on package.json. The Wave-1 scaffold plan owns the entire scripts block; later plans add config files and source files only."
- "Firewall-as-directory pattern: src/{sim,render,ui,save,content,audio,store}/.gitkeep is the structural prerequisite for Plan 02's ESLint boundaries rule. Empty directories cannot exist in git, so .gitkeep is mandatory."
requirements-completed: [CORE-01]
# Metrics
duration: 6min
completed: 2026-05-09
---
# Phase 1 Plan 01: Scaffold and Test Infrastructure Summary
**Hand-built Phaser 4 + React 19 + Vite 8 + TypeScript 6 scaffold mirroring the official `@phaserjs/game` template, all 15 Phase-1 deps installed at locked versions, seven architectural-firewall directories under src/, repo-root /content/ + /assets/ trees, pre-declared package.json scripts for Wave 2, and Vitest (happy-dom) + Playwright wired with a passing sentinel test.**
## Performance
- **Duration:** 6 min
- **Started:** 2026-05-09T03:12:34Z
- **Completed:** 2026-05-09T03:18:51Z
- **Tasks:** 2 (both completed atomically)
- **Files created:** 28 source/config files + 11 .gitkeep markers (no files modified — greenfield)
## Accomplishments
- **Buildable scaffold.** `npm run build` runs `tsc -b && vite build` and produces `dist/index.html` + `dist/assets/index-*.js` (~1.5MB Phaser bundle, code-split deferred to Phase 2+ when actual scenes land). TypeScript strict mode passes on all source.
- **All Phase-1 dependencies installed at the verified versions.** 15 listed in package.json (10 production + 5 dev that exceed the trivial baseline; total devDependencies count is 14 with @types/* and tooling). Versions match RESEARCH.md exactly.
- **All seven architectural-firewall directories exist under src/** with .gitkeep markers. Plan 02's `eslint-plugin-boundaries` rule has clean targets to lint against.
- **Repo-root /content/ and /assets/ trees created** per CONTEXT D-11, D-12. Single package, no monorepo. content/ has /dialogue/ and /seasons/ subdirs ready for Phase 2 authored fragments.
- **All downstream-required scripts pre-declared in package.json:** `dev`, `build`, `preview`, `lint` (with `--max-warnings 0` per CI Pitfall C), `test` (with `--passWithNoTests=false` per CI Pitfall B), `test:watch`, `validate:assets`, `compile:ink` (no-op stub for Phase 1), `ci` (composite gate). Wave-2 plans 0206 can now run in parallel without colliding on this file.
- **Vitest + Playwright wired and proven.** Sentinel test passes in 593ms; `npx playwright --version` returns `Version 1.59.1` (matching the locked version exactly).
## Task Commits
Each task was committed atomically on `master`:
1. **Task 1: Scaffold Phaser 4 template + Phase-1 deps + firewall directories**`df7d687` (chore)
2. **Task 2: Configure Vitest (happy-dom) + Playwright + sentinel test**`7b2982b` (chore)
**Plan metadata:** _(this commit)_`docs(01-01): complete scaffold and test infra plan`
## Final package.json scripts block (load-bearing for Wave 2)
```json
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint . --max-warnings 0",
"test": "vitest run --passWithNoTests=false",
"test:watch": "vitest",
"validate:assets": "node scripts/validate-assets.mjs",
"compile:ink": "echo \"[compile:ink] no .ink files yet — Phase 2 will populate /content/dialogue/\" && exit 0",
"ci": "npm run lint && npm run test && npm run validate:assets && npm run build"
}
}
```
Wave-2 plans should rely on these script keys *as written* and only edit them if a fundamental shape change is required. Plan 02 will add an `eslint.config.js` (the `lint` script invokes it); Plan 03 will add migration tests under `src/save/` (the `test` script picks them up via the `vitest.config.ts` include glob); Plan 05 will write `scripts/validate-assets.mjs`; Plan 07 will compose all of these into the CI workflow.
## Final installed dependency versions
Resolved versions (`npm ls --depth=0` would show these, all matching the RESEARCH.md targets within the `^` range):
| Package | Range in package.json | Notes |
|---|---|---|
| `phaser` | `^4.1.0` | "Salusa", April 2026; latest tag on npm |
| `react` / `react-dom` | `^19.2.6` | React 19.2.x |
| `vite` | `^8.0.11` | Vite 8 (Rolldown) |
| `@vitejs/plugin-react` | `^6.0.1` | matches Vite 8 |
| `typescript` | `^6.0.3` | TS 6 |
| `idb` | `^8.0.3` | promise-based IndexedDB wrapper (Plan 03) |
| `lz-string` | `^1.5.0` | save compression + Base64 (Plan 03) |
| `zod` | `^4.4.3` | Zod 4 (NOT Zod 3) — Plan 03/04 |
| `crc-32` | `^1.2.2` | save checksum (Plan 03) |
| `gray-matter` | `^4.0.3` | .md frontmatter (Plan 04) |
| `yaml` | `^2.8.4` | yaml content (Plan 04) |
| `inkjs` | `^2.4.0` | Ink runtime (Phase 2 — installed only) |
| `vitest` | `^4.1.5` | Vitest 4 |
| `@vitest/ui` | `^4.1.5` | Vitest UI |
| `happy-dom` | `^20.9.0` | Vitest DOM env |
| `fake-indexeddb` | `^6.2.5` | Plan 03 IDB tests (pre-installed) |
| `@playwright/test` | `^1.59.1` | matches research lock exactly |
| `eslint` | `^9` | Plan 02 host |
| `eslint-plugin-boundaries` | `^6.0.2` | Plan 02 firewall rule |
| `inklecate` | `^1.8.1` | Phase 2 Ink compiler — installed only |
| `@types/react` / `@types/react-dom` / `@types/node` | `^19` / `^19` / `^22` | TS types |
## Decisions Made
- **Manual scaffold over interactive Phaser scaffolder.** `npm create @phaserjs/game@latest` (create-game v1.3.2) is interactive-only — the `--template react-ts --yes` flags documented in the plan are silently ignored by the current version. Verified by reading the published tarball: the React TS template ships only `App.tsx`; the rest of the tree is composed dynamically inside the interactive flow. The plan's Step 1 explicitly authorizes the fallback ("If the official scaffolder cannot run non-interactively, fall back to manually constructing the equivalent file tree per RESEARCH.md").
- **Referenced-projects tsconfig layout.** Adopted current Vite + TS 6 best practice: root `tsconfig.json` with `references` to `tsconfig.app.json` (covers `src/`, DOM lib) and `tsconfig.node.json` (covers `vite.config.ts`, `vitest.config.ts`, `playwright.config.ts`, `scripts/**/*.mjs`). Strict mode enforced at all three layers — TypeScript-strict invariant from CLAUDE.md "Code Style".
- **`build` script runs `tsc -b && vite build`.** Not bare `vite build`, so the strict-TS gate runs on every build instead of being silently bypassed by Vite's loose default transpile.
- **`*.tsbuildinfo` added to .gitignore.** TS 6's `tsc -b` emits these incremental-build cache files at the project root; they are dev artifacts, not source.
- **Sentinel test asserts both `1+1===2` AND `typeof globalThis.window === 'object'`.** Doubles as proof that happy-dom is active so Plan 03 can trust the env when its IDB tests load.
- **`fake-indexeddb@^6` pre-installed in Plan 01** rather than deferring to Plan 03. Defends Wave-2 parallel execution: Plan 03 Task 2 can `import 'fake-indexeddb/auto'` without re-editing package.json.
## Drift from official Phaser template
For Plan 02's awareness:
- **Vite version installed: 8.0.11.** The Phaser official template's package.json (per the v1.3.2 tarball's React TS scaffolding fragments) lists older Vite versions; the npm registry's current `vite@latest` is 8.0.11. We took latest within the `^8` range, matching RESEARCH.md.
- **ESLint version installed: 9.x (^9.39.4 resolved).** ESLint 9 uses the **flat config** format (`eslint.config.js`), NOT the legacy `.eslintrc.cjs`. **Plan 02 must write `eslint.config.js`, not `.eslintrc.*`.** No legacy ESLint config was written by this plan.
- **No `public/` directory created yet.** The official Phaser template ships a `public/assets/` for the demo game's images. Phase 1 has no game assets, so I omitted `public/` entirely. Plan 02+ may create it if needed; nothing in the current scaffold references it.
- **No `eslint.config.js` created here.** Plan 02 owns the lint config. The `lint` script will fail until Plan 02 lands — by design (the script key exists so Plan 02 doesn't re-edit package.json).
- **`scripts/validate-assets.mjs` does not exist yet.** Plan 05 owns it. The `validate:assets` script will fail until Plan 05 lands — by design.
## Files Created
(28 source/config files + 11 .gitkeep markers)
- `package.json`, `package-lock.json`, `.gitignore` — npm + ignore rules
- `index.html` — Vite HTML entry
- `vite.config.ts` — Vite config (React plugin)
- `tsconfig.json`, `tsconfig.app.json`, `tsconfig.node.json` — strict TS, referenced projects
- `src/main.tsx`, `src/App.tsx`, `src/PhaserGame.tsx`, `src/vite-env.d.ts` — React entry + Phaser bridge
- `src/game/main.ts`, `src/game/scenes/Boot.ts` — minimal Phaser config + placeholder scene
- `src/{sim,render,ui,save,content,audio,store}/.gitkeep` — 7 architectural-firewall markers
- `content/.gitkeep`, `content/dialogue/.gitkeep`, `content/seasons/.gitkeep` — repo-root authored-content tree
- `assets/.gitkeep` — repo-root AI-asset tree
- `vitest.config.ts` — Vitest happy-dom env
- `playwright.config.ts` — Playwright config (no specs)
- `src/__sentinel__.test.ts` — sentinel test
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Used hand-built scaffold instead of `npm create @phaserjs/game@latest`**
- **Found during:** Task 1 (Step 1 of the action block)
- **Issue:** The Phaser official scaffolder `@phaserjs/create-game@1.3.2` is interactive-only — the `--template react-ts --yes` flags documented in the plan are silently ignored, and piping arrow-key sequences into stdin advances past the first prompt but the scaffolder hangs at "Select Template" because subsequent prompts depend on stdin staying open.
- **Fix:** Took the documented fallback path in plan Step 1 ("If the official scaffolder cannot run non-interactively, fall back to manually constructing the equivalent file tree per RESEARCH.md"). Built the equivalent file tree by hand (index.html, vite.config.ts, tsconfig*, src/main.tsx, src/App.tsx, src/PhaserGame.tsx, src/game/main.ts, src/game/scenes/Boot.ts) using the published create-game tarball's React TS template fragment as the source-of-truth shape, with current `vite@latest` and `typescript@latest` resolved versions matching RESEARCH.md.
- **Files modified:** All Task 1 files.
- **Verification:** `npm run build` produces `dist/`; `npm test` runs the sentinel test (1 pass, 593ms).
- **Committed in:** `df7d687` (Task 1 commit).
**2. [Rule 2 - Missing Critical] `build` script wraps `tsc -b` before `vite build`**
- **Found during:** Task 1 (Step 6, package.json scripts)
- **Issue:** The plan's Step 6 specifies `"build": "vite build"`. But Vite's default transpile is loose — it does NOT enforce TypeScript strict-mode errors at build time. CLAUDE.md "Code Style" mandates TypeScript strict; the `tsc -b` step is the only thing that actually enforces it on every build.
- **Fix:** Set `"build": "tsc -b && vite build"` so the strict-TS gate runs first and blocks the bundler if types fail.
- **Files modified:** package.json (scripts.build).
- **Verification:** `npm run build` runs both steps and exits 0.
- **Committed in:** `df7d687` (Task 1 commit).
**3. [Rule 2 - Missing Critical] Added `*.tsbuildinfo` to .gitignore**
- **Found during:** Task 1 (Step 8, .gitignore verification — discovered when `git status --short` showed two untracked `tsconfig.*.tsbuildinfo` files after running `tsc -b`)
- **Issue:** TS 6's `tsc -b` emits incremental-build cache files at the project root. The plan's Step 8 lists `node_modules/`, `dist/`, `coverage/` but omits `*.tsbuildinfo`. Leaving them untracked-but-uncommitted is fine, but a future `git add -A` (which the plan correctly forbids in Step 9) would commit them as noise.
- **Fix:** Added `*.tsbuildinfo` to .gitignore alongside `dist/`.
- **Files modified:** .gitignore.
- **Verification:** `git status --short` no longer shows the two `.tsbuildinfo` files after the change.
- **Committed in:** `df7d687` (Task 1 commit).
---
**Total deviations:** 3 auto-fixed (1 blocking, 2 missing critical)
**Impact on plan:** All three deviations are mechanical / safety-net additions explicitly authorized by the plan's own fallback language or by CLAUDE.md "Code Style" / RESEARCH.md "CI Pitfalls". No scope creep, no architectural change. Wave-2 plans are unaffected.
## Issues Encountered
- **`npm create @phaserjs/game@latest` is interactive-only.** Documented as a deviation above. Time cost: ~1 min for two probe attempts before falling back; the fallback path itself was straightforward.
- **No other issues.** Sentinel test passed first try; `npm run build` passed first try.
## Authentication Gates
None — Phase 1 scaffolds tooling only; no external auth needed.
## Threat Flags
None — Phase 1 is build/dev tooling only. The two threat-model entries identified for Phase 1 (save tampering, malformed Base64 import DoS) are owned by Plan 03. The `npm install` supply-chain consideration is mitigated by `package-lock.json` being committed (verified by `test -f package-lock.json` — passes).
## Known Stubs
- **`compile:ink` script is a no-op echo + `exit 0`.** Per CONTEXT D-08 / RESEARCH § "Pattern 4 — Ink files in Phase 1": Phase 2 will replace this with `inklecate -o src/content/compiled-ink/ content/dialogue/*.ink` once authored Ink files exist. The script key exists in Phase 1 only so Wave-2 plans don't have to re-edit package.json.
- **`scripts/validate-assets.mjs` does not exist yet.** Plan 05 (asset provenance) owns it. The `validate:assets` script will fail until Plan 05 lands — by design.
- **`eslint.config.js` does not exist yet.** Plan 02 (firewall + lint) owns it. The `lint` script will fail until Plan 02 lands — by design.
- **`tests/e2e/` directory and the first Playwright spec do not exist yet.** Phase 2 PIPE-07 owns the first e2e smoke spec. Playwright config is wired so the spec can land without re-editing config.
- **`src/__sentinel__.test.ts` is a sentinel only.** It exists to prove the runner works and SHOULD be deleted when real tests exist (Plan 03 onward). Documented in the file's header comment.
These are all *intentional* stubs per the plan's "pre-declared scripts" pattern — they exist to avoid Wave-2 plans colliding on package.json. They are tracked here so the verifier doesn't flag them as omissions.
## Next Plan Readiness
- **Plan 02 (firewall + lint):** Ready. Has clean `src/{sim,render,ui,save,content,audio,store}/` targets to lint against. ESLint 9 + `eslint-plugin-boundaries@^6.0.2` already installed; Plan 02 only writes `eslint.config.js` and any sentinel test files. **Plan 02 must use ESLint 9 flat-config format (`eslint.config.js`), not legacy `.eslintrc.*`.**
- **Plan 03 (save layer):** Ready. `idb`, `lz-string`, `zod`, `crc-32`, `vitest`, `happy-dom`, `fake-indexeddb` all installed. The `vitest.config.ts` `include` glob will auto-discover `src/save/**/*.test.ts` files Plan 03 drops in. Sentinel test confirms `globalThis.window` exists in the test env.
- **Plan 04 (content pipeline):** Ready. `gray-matter`, `yaml`, `zod`, `inkjs` all installed. `src/content/` and `content/` trees exist; Plan 04 fills `src/content/schemas/` and `src/content/loader.ts`.
- **Plan 05 (asset provenance):** Ready. `assets/` tree exists; `scripts/validate-assets.mjs` is the gap (Plan 05 owns it). The `validate:assets` script key is pre-declared.
- **Plan 06 (doctrine docs):** Ready — pure markdown plan, no code dependencies.
- **Plan 07 (CI workflow):** Ready. All script keys pre-declared so Plan 07 only writes `.github/workflows/ci.yml` (or equivalent) that calls `npm ci && npm run ci`.
No blockers; Wave 2 can execute in parallel as planned.
## Self-Check
- [x] `package.json` exists at repo root and contains 15+ Phase-1 deps — verified.
- [x] All 7 firewall directories exist with .gitkeep — verified.
- [x] Repo-root /content/ + /assets/ exist — verified.
- [x] All 9 pre-declared scripts present in package.json — verified.
- [x] `npm run build` exits 0 — verified.
- [x] `tsconfig.json` contains `"strict": true` — verified.
- [x] `vitest.config.ts` exists with happy-dom + passWithNoTests:false — verified.
- [x] `playwright.config.ts` exists with testDir 'tests/e2e' — verified.
- [x] `src/__sentinel__.test.ts` passes — verified (1 pass, 593ms).
- [x] `npx playwright --version` = Version 1.59.1 — verified.
- [x] Task 1 commit exists: `df7d687` — verified in `git log`.
- [x] Task 2 commit exists: `7b2982b` — verified in `git log`.
**## Self-Check: PASSED**
---
*Phase: 01-foundations-and-doctrine*
*Plan: 01 of 7*
*Completed: 2026-05-09*