@codyswann/lisa 2.180.1 → 2.182.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/configs/eslint/phaser.d.ts.map +1 -1
  2. package/dist/configs/eslint/phaser.js +5 -0
  3. package/dist/configs/eslint/phaser.js.map +1 -1
  4. package/package.json +1 -1
  5. package/phaser/create-only/tsconfig.local.json +4 -0
  6. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa/rules/eager/verification.md +6 -0
  9. package/plugins/lisa/rules/reference/verification.md +47 -0
  10. package/plugins/lisa/skills/codify-verification/SKILL.md +10 -0
  11. package/plugins/lisa-agy/plugin.json +1 -1
  12. package/plugins/lisa-agy/skills/codify-verification/SKILL.md +10 -0
  13. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  16. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-copilot/rules/eager/verification.md +6 -0
  20. package/plugins/lisa-copilot/rules/reference/verification.md +47 -0
  21. package/plugins/lisa-copilot/skills/codify-verification/SKILL.md +10 -0
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/rules/verification-reference.mdc +47 -0
  24. package/plugins/lisa-cursor/rules/verification.mdc +6 -0
  25. package/plugins/lisa-cursor/skills/codify-verification/SKILL.md +10 -0
  26. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-agy/plugin.json +1 -1
  29. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser/skills/phaser-build-deploy/SKILL.md +9 -4
  49. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  50. package/plugins/lisa-phaser-agy/skills/phaser-build-deploy/SKILL.md +9 -4
  51. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser-copilot/skills/phaser-build-deploy/SKILL.md +9 -4
  53. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-cursor/skills/phaser-build-deploy/SKILL.md +9 -4
  55. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails-agy/plugin.json +1 -1
  58. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  63. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  68. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/src/base/rules/eager/verification.md +6 -0
  71. package/plugins/src/base/rules/reference/verification.md +47 -0
  72. package/plugins/src/base/skills/codify-verification/SKILL.md +10 -0
  73. package/plugins/src/phaser/skills/phaser-build-deploy/SKILL.md +9 -4
  74. package/tsconfig/phaser.json +2 -2
  75. package/typescript/copy-overwrite/scripts/check-verification-coverage.mjs +159 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -31,7 +31,10 @@ export default defineConfig({
31
31
  terserOptions: { compress: { passes: 2, drop_console: true }, format: { comments: false } },
32
32
  rollupOptions: {
33
33
  output: {
34
- manualChunks: { phaser: ["phaser"] }, // engine in its own long-lived chunk
34
+ // Vite 8's rolldown bundler requires the FUNCTION form (the object form
35
+ // `{ phaser: ["phaser"] }` throws "manualChunks is not a function").
36
+ manualChunks: id =>
37
+ id.includes("node_modules/phaser") ? "phaser" : undefined,
35
38
  entryFileNames: "assets/[name]-[hash].js", // content hash on everything
36
39
  chunkFileNames: "assets/[name]-[hash].js",
37
40
  assetFileNames: "assets/[name]-[hash][extname]",
@@ -41,9 +44,11 @@ export default defineConfig({
41
44
  });
42
45
  ```
43
46
 
44
- `manualChunks: { phaser: ["phaser"] }` is the single highest-value line: Phaser is
45
- hundreds of KB and rarely changes, so isolating it means a gameplay tweak only
46
- busts the small app chunk. Two-pass terser squeezes meaningfully more than one.
47
+ The `manualChunks` function is the single highest-value line: Phaser is hundreds
48
+ of KB and rarely changes, so isolating it means a gameplay tweak only busts the
49
+ small app chunk. Use the **function** form Vite 8 ships the rolldown bundler,
50
+ which requires it (the classic object form throws `manualChunks is not a
51
+ function`). Two-pass terser squeezes meaningfully more than one.
47
52
 
48
53
  ## Asset hashing and immutable caching
49
54
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -31,7 +31,10 @@ export default defineConfig({
31
31
  terserOptions: { compress: { passes: 2, drop_console: true }, format: { comments: false } },
32
32
  rollupOptions: {
33
33
  output: {
34
- manualChunks: { phaser: ["phaser"] }, // engine in its own long-lived chunk
34
+ // Vite 8's rolldown bundler requires the FUNCTION form (the object form
35
+ // `{ phaser: ["phaser"] }` throws "manualChunks is not a function").
36
+ manualChunks: id =>
37
+ id.includes("node_modules/phaser") ? "phaser" : undefined,
35
38
  entryFileNames: "assets/[name]-[hash].js", // content hash on everything
36
39
  chunkFileNames: "assets/[name]-[hash].js",
37
40
  assetFileNames: "assets/[name]-[hash][extname]",
@@ -41,9 +44,11 @@ export default defineConfig({
41
44
  });
42
45
  ```
43
46
 
44
- `manualChunks: { phaser: ["phaser"] }` is the single highest-value line: Phaser is
45
- hundreds of KB and rarely changes, so isolating it means a gameplay tweak only
46
- busts the small app chunk. Two-pass terser squeezes meaningfully more than one.
47
+ The `manualChunks` function is the single highest-value line: Phaser is hundreds
48
+ of KB and rarely changes, so isolating it means a gameplay tweak only busts the
49
+ small app chunk. Use the **function** form Vite 8 ships the rolldown bundler,
50
+ which requires it (the classic object form throws `manualChunks is not a
51
+ function`). Two-pass terser squeezes meaningfully more than one.
47
52
 
48
53
  ## Asset hashing and immutable caching
49
54
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -31,7 +31,10 @@ export default defineConfig({
31
31
  terserOptions: { compress: { passes: 2, drop_console: true }, format: { comments: false } },
32
32
  rollupOptions: {
33
33
  output: {
34
- manualChunks: { phaser: ["phaser"] }, // engine in its own long-lived chunk
34
+ // Vite 8's rolldown bundler requires the FUNCTION form (the object form
35
+ // `{ phaser: ["phaser"] }` throws "manualChunks is not a function").
36
+ manualChunks: id =>
37
+ id.includes("node_modules/phaser") ? "phaser" : undefined,
35
38
  entryFileNames: "assets/[name]-[hash].js", // content hash on everything
36
39
  chunkFileNames: "assets/[name]-[hash].js",
37
40
  assetFileNames: "assets/[name]-[hash][extname]",
@@ -41,9 +44,11 @@ export default defineConfig({
41
44
  });
42
45
  ```
43
46
 
44
- `manualChunks: { phaser: ["phaser"] }` is the single highest-value line: Phaser is
45
- hundreds of KB and rarely changes, so isolating it means a gameplay tweak only
46
- busts the small app chunk. Two-pass terser squeezes meaningfully more than one.
47
+ The `manualChunks` function is the single highest-value line: Phaser is hundreds
48
+ of KB and rarely changes, so isolating it means a gameplay tweak only busts the
49
+ small app chunk. Use the **function** form Vite 8 ships the rolldown bundler,
50
+ which requires it (the classic object form throws `manualChunks is not a
51
+ function`). Two-pass terser squeezes meaningfully more than one.
47
52
 
48
53
  ## Asset hashing and immutable caching
49
54
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -31,7 +31,10 @@ export default defineConfig({
31
31
  terserOptions: { compress: { passes: 2, drop_console: true }, format: { comments: false } },
32
32
  rollupOptions: {
33
33
  output: {
34
- manualChunks: { phaser: ["phaser"] }, // engine in its own long-lived chunk
34
+ // Vite 8's rolldown bundler requires the FUNCTION form (the object form
35
+ // `{ phaser: ["phaser"] }` throws "manualChunks is not a function").
36
+ manualChunks: id =>
37
+ id.includes("node_modules/phaser") ? "phaser" : undefined,
35
38
  entryFileNames: "assets/[name]-[hash].js", // content hash on everything
36
39
  chunkFileNames: "assets/[name]-[hash].js",
37
40
  assetFileNames: "assets/[name]-[hash][extname]",
@@ -41,9 +44,11 @@ export default defineConfig({
41
44
  });
42
45
  ```
43
46
 
44
- `manualChunks: { phaser: ["phaser"] }` is the single highest-value line: Phaser is
45
- hundreds of KB and rarely changes, so isolating it means a gameplay tweak only
46
- busts the small app chunk. Two-pass terser squeezes meaningfully more than one.
47
+ The `manualChunks` function is the single highest-value line: Phaser is hundreds
48
+ of KB and rarely changes, so isolating it means a gameplay tweak only busts the
49
+ small app chunk. Use the **function** form Vite 8 ships the rolldown bundler,
50
+ which requires it (the classic object form throws `manualChunks is not a
51
+ function`). Two-pass terser squeezes meaningfully more than one.
47
52
 
48
53
  ## Asset hashing and immutable caching
49
54
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.180.1",
3
+ "version": "2.182.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -4,12 +4,18 @@
4
4
 
5
5
  **Verification is using the resulting software the way a user would** — interacting with the UI, calling the API, running the CLI, observing behavior. Tests pass in isolation; verification proves the system works as a whole.
6
6
 
7
+ **Verification IS UAT** (User Acceptance Testing) — the same single gate, not two concepts. "Use the software as a user, against the acceptance criteria" *is* UAT. Everything below makes that one process concrete: a committed evidence artifact, a codified check that re-runs in CI, and a per-change requirement.
8
+
7
9
  ## Mandatory
8
10
 
9
11
  - **Never claim success without runtime evidence.** "The code looks correct" is not evidence.
10
12
  - **If all you did was run tests, typecheck, and lint — you have NOT verified.**
11
13
  - **Before starting implementation, state your verification plan** — how you will USE the resulting software to prove it works. A plan that only lists `test`/`typecheck`/`lint` commands is not a plan. Do not begin until confirmed.
12
14
  - **After verifying empirically, codify it as a regression test** via the `codify-verification` skill — Playwright for UI, integration test for API/DB/auth, benchmark for performance. Codification is mandatory for every verification type except PR/Documentation/Deploy and Investigate-Only spikes.
15
+ - **The codified proof re-runs in CI.** Codified runtime verification lives where the project's e2e/Playwright tests live (`tests/e2e/**`) and runs as a required CI check for types with verification enforced — so the proof re-runs on every PR, not once by hand.
16
+ - **Commit the evidence.** Write a durable artifact to `evidence/<ticket>/` — the acceptance criteria with per-criterion pass/fail + note, the observed state, screenshots/recording, and a `verdict.json`. The transient `.lisa/verification-status.json` is the session gate; `evidence/<ticket>/` is the committed proof.
17
+ - **Per-change verification is mandatory.** Every `feat`/`fix` adds or extends a verification (e2e) spec mapped to its acceptance criteria. The only exception is a genuinely non-behavioral change explicitly marked with the **logged** `verification-exempt` label — never a silent skip.
18
+ - **Drive the playthrough with `/lisa:product-walkthrough`** (it walks the live product through a real browser); each project type plugs in its own drive mechanism (e.g. a Phaser game is driven through Playwright + an in-game verification test bridge that seeds RNG, reads state, injects input, and steps frames).
13
19
  - **Every PR must include reviewer replay steps** — the exact human steps to use the software and confirm the change works. Not test commands. If a reviewer can't reproduce from the PR description alone, the PR is incomplete.
14
20
  - **Exhaust credential sources before deferring runtime verification** — check project e2e / Playwright config and fixtures first, then `.lisa.config.local.json` / environment variables, then documented ticket credentials such as `Sign-in Required`.
15
21
  - **Never mark required runtime verification done on artifact-only evidence** — if credentials are genuinely unavailable after all sources are checked, post the blocker, transition the item to the configured blocked state, apply the configured `needs-human` / `human-review` label, and label the evidence as `artifact-only / verification deferred`.
@@ -135,4 +135,51 @@ Evidence and summaries must explicitly distinguish `verified empirically` from `
135
135
 
136
136
  ---
137
137
 
138
+ ## Making verification concrete (UAT)
139
+
140
+ Verification **is** UAT — one gate, not two. This section makes the single
141
+ verification process concrete so "an agent actually exercised the running
142
+ software against the acceptance criteria" is durable and re-checkable, not a
143
+ one-off claim. It builds on the **Per-Work-Unit Evidence Contract** above (the
144
+ `[EVIDENCE: name]` manifest), adding three concrete requirements:
145
+
146
+ **1. The codified proof re-runs in CI.** After local verification passes,
147
+ `codify-verification` encodes it where the project's e2e/Playwright tests live
148
+ (`tests/e2e/**`). That suite runs in CI (the existing e2e/Playwright job); for a
149
+ project type with verification **enforced**, it is a required check — so the proof
150
+ re-runs on every PR instead of being proven once by hand.
151
+
152
+ **2. Evidence is committed to the repo.** The named artifacts from the work
153
+ unit's evidence manifest are committed under `evidence/<ticket>/` (in addition to
154
+ any tracker attachment), so the proof lives with the code and is reviewable in the
155
+ PR:
156
+
157
+ ```
158
+ evidence/<ticket>/
159
+ verdict.json # acceptance criteria + per-criterion pass/fail + note,
160
+ # overall verdict, ticket, commit, lisaVersion, agent, stampedAt
161
+ state.json # observed runtime state asserted against
162
+ screenshots/*.png # downscaled, capped; key frames of the playthrough
163
+ ```
164
+
165
+ `evidence/**` is ignored by lint/format/knip/coverage — it is proof, not source.
166
+
167
+ **3. Per-change verification is enforced.** Every `feat`/`fix` adds or extends a
168
+ verification (e2e) spec mapped to its acceptance criteria. CI's
169
+ `verification-coverage` check requires that delta. The sole exception is a
170
+ genuinely non-behavioral change carrying the **logged** `verification-exempt`
171
+ label — the check prints why it was exempted; it is never a silent skip.
172
+
173
+ **Playthrough driver.** `/lisa:product-walkthrough` is the shared driver (it walks
174
+ the live product through a real browser). Each project type supplies its own drive
175
+ mechanism: a Phaser game is driven through Playwright + an in-game verification
176
+ test bridge (seed RNG, read state, inject semantic input, step frames); API types
177
+ drive HTTP against a running service; etc.
178
+
179
+ **Rollout.** Enforcement is per-type and flag-gated (the `verify_enforced`
180
+ workflow input, default off) so a type without a drive mechanism is never wedged.
181
+ Phaser is the first enforced type.
182
+
183
+ ---
184
+
138
185
  For the full verification lifecycle (classify, check tooling, plan, execute, loop), surfaces, escalation protocol, and proof artifact requirements, see the `verification-lifecycle` skill loaded by the `verification-specialist` agent.
@@ -88,6 +88,16 @@ For Playwright UI tests specifically:
88
88
  - Capture a trace or screenshot only if the project's existing tests do; do not invent a new artifact convention
89
89
  - Mirror the project's existing config for base URL, retries, and test isolation
90
90
 
91
+ **Concrete verification (UAT) contract.** Verification *is* UAT — codifying it is
92
+ how the playthrough becomes durable. For a runtime/behavioral `feat`/`fix`: place
93
+ the codified test where the project's e2e/Playwright tests live (`tests/e2e/**`)
94
+ so CI re-runs it, and commit the evidence artifact to `evidence/<ticket>/`
95
+ (`verdict.json` + state + screenshots). For a Phaser game, drive the canvas
96
+ through the in-game verification test bridge (seed RNG, read state, inject input,
97
+ step frames) with deterministic rendering. CI's `verification-coverage` check
98
+ requires a verification-spec delta on every behavioral change. See the
99
+ `reference/verification.md` "Making verification concrete (UAT)" section.
100
+
91
101
  ### 4. Run the test in isolation
92
102
 
93
103
  Run only the new test, using whatever per-test invocation the project supports:
@@ -31,7 +31,10 @@ export default defineConfig({
31
31
  terserOptions: { compress: { passes: 2, drop_console: true }, format: { comments: false } },
32
32
  rollupOptions: {
33
33
  output: {
34
- manualChunks: { phaser: ["phaser"] }, // engine in its own long-lived chunk
34
+ // Vite 8's rolldown bundler requires the FUNCTION form (the object form
35
+ // `{ phaser: ["phaser"] }` throws "manualChunks is not a function").
36
+ manualChunks: id =>
37
+ id.includes("node_modules/phaser") ? "phaser" : undefined,
35
38
  entryFileNames: "assets/[name]-[hash].js", // content hash on everything
36
39
  chunkFileNames: "assets/[name]-[hash].js",
37
40
  assetFileNames: "assets/[name]-[hash][extname]",
@@ -41,9 +44,11 @@ export default defineConfig({
41
44
  });
42
45
  ```
43
46
 
44
- `manualChunks: { phaser: ["phaser"] }` is the single highest-value line: Phaser is
45
- hundreds of KB and rarely changes, so isolating it means a gameplay tweak only
46
- busts the small app chunk. Two-pass terser squeezes meaningfully more than one.
47
+ The `manualChunks` function is the single highest-value line: Phaser is hundreds
48
+ of KB and rarely changes, so isolating it means a gameplay tweak only busts the
49
+ small app chunk. Use the **function** form Vite 8 ships the rolldown bundler,
50
+ which requires it (the classic object form throws `manualChunks is not a
51
+ function`). Two-pass terser squeezes meaningfully more than one.
47
52
 
48
53
  ## Asset hashing and immutable caching
49
54
 
@@ -2,8 +2,8 @@
2
2
  "extends": "./typescript.json",
3
3
  "compilerOptions": {
4
4
  "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
- "rootDir": ".",
6
- "outDir": "dist",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
7
  "resolveJsonModule": true,
8
8
  "allowJs": false,
9
9
  "skipLibCheck": true,
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-verification-coverage — per-change verification (UAT) gate.
4
+ *
5
+ * Verification IS UAT — one gate, not two. This fails a feat/fix change that
6
+ * ships no verification-spec delta (the project's e2e/Playwright tests, where
7
+ * `codify-verification` lands the codified playthrough), so every behavioral
8
+ * change carries an acceptance test an agent's verification produced. A genuinely
9
+ * non-behavioral change may carry the `verification-exempt` label, which this
10
+ * check honors but LOGS (never a silent skip).
11
+ *
12
+ * Inputs (all via env, CI-friendly):
13
+ * VERIFY_BASE_SHA diff base (else falls back to `origin/<VERIFY_BASE_REF|main>...HEAD`)
14
+ * VERIFY_HEAD_SHA diff head (default HEAD)
15
+ * VERIFY_BASE_REF base branch for the fallback range (default main)
16
+ * VERIFY_CHANGE_TYPES comma list of conventional-commit types in the change
17
+ * (e.g. "feat,chore"); if empty, derived from commit subjects
18
+ * VERIFY_LABELS comma list of PR labels (for `verification-exempt`)
19
+ *
20
+ * Exit 0 = satisfied / exempt / not-required. Exit 1 = required but missing.
21
+ * @module scripts/check-verification-coverage
22
+ */
23
+ import { execSync } from "node:child_process";
24
+ import { pathToFileURL } from "node:url";
25
+
26
+ const BEHAVIORAL_TYPES = new Set(["feat", "fix"]);
27
+ const EXEMPT_LABEL = "verification-exempt";
28
+ // A verification spec lives in a top-level e2e/ dir, a nested tests/e2e/ tree,
29
+ // or a tests/verification/ tree — NOT an arbitrary path that merely contains an
30
+ // "e2e" segment (e.g. src/e2e/helpers.ts must not satisfy the gate).
31
+ const VERIFICATION_PATH = /^e2e\/|(^|\/)tests\/(e2e|verification)\//;
32
+
33
+ /**
34
+ * Pure decision: is a verification-spec delta required, and is it satisfied?
35
+ * @param {object} input - Evaluation input
36
+ * @param {string[]} input.changedFiles - Paths changed in the range
37
+ * @param {string[]} input.changeTypes - Conventional-commit types present
38
+ * @param {string[]} input.labels - PR labels
39
+ * @returns {{required: boolean, ok: boolean, exempt?: boolean, reason: string}} Verdict
40
+ */
41
+ export function evaluateVerificationCoverage({
42
+ changedFiles,
43
+ changeTypes,
44
+ labels,
45
+ }) {
46
+ const isBehavioral = changeTypes.some(type => BEHAVIORAL_TYPES.has(type));
47
+ const isExempt = labels.includes(EXEMPT_LABEL);
48
+ const hasDelta = changedFiles.some(file => VERIFICATION_PATH.test(file));
49
+
50
+ if (!isBehavioral) {
51
+ return {
52
+ required: false,
53
+ ok: true,
54
+ reason: "No feat/fix change — a verification-spec delta is not required.",
55
+ };
56
+ }
57
+ if (isExempt) {
58
+ return {
59
+ required: true,
60
+ ok: true,
61
+ exempt: true,
62
+ reason: `Behavioral change exempted by the '${EXEMPT_LABEL}' label.`,
63
+ };
64
+ }
65
+ if (hasDelta) {
66
+ return {
67
+ required: true,
68
+ ok: true,
69
+ reason: "Behavioral change ships a verification (e2e) spec delta.",
70
+ };
71
+ }
72
+ return {
73
+ required: true,
74
+ ok: false,
75
+ reason: `Behavioral change (feat/fix) ships NO verification (e2e) spec and is not labeled '${EXEMPT_LABEL}'.`,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Gather the change context from git + env.
81
+ * @returns {{changedFiles: string[], changeTypes: string[], labels: string[]}} Context
82
+ */
83
+ function gatherContext() {
84
+ const head = process.env.VERIFY_HEAD_SHA || "HEAD";
85
+ const baseRef = process.env.VERIFY_BASE_REF || "main";
86
+ const base = process.env.VERIFY_BASE_SHA || `origin/${baseRef}`;
87
+ // Three-dot diff = the PR's "files changed" (merge-base), matching GitHub.
88
+ // Two-dot log = the commits the PR introduces (not both tips).
89
+ const diffRange = `${base}...${head}`;
90
+ const logRange = `${base}..${head}`;
91
+
92
+ const runGit = (cmd, fallback) => {
93
+ try {
94
+ return execSync(cmd, { encoding: "utf8" });
95
+ } catch {
96
+ return fallback;
97
+ }
98
+ };
99
+
100
+ const changedFiles = runGit(`git diff --name-only ${diffRange}`, "")
101
+ .split("\n")
102
+ .map(line => line.trim())
103
+ .filter(Boolean);
104
+
105
+ const fromEnv = (process.env.VERIFY_CHANGE_TYPES || "")
106
+ .split(",")
107
+ .map(value => value.trim().toLowerCase())
108
+ .filter(Boolean);
109
+ const fromCommits = fromEnv.length
110
+ ? []
111
+ : runGit(`git log --format=%s ${logRange}`, "")
112
+ .split("\n")
113
+ .map(subject => {
114
+ const match = subject.match(/^(\w+)[(!:]/);
115
+ return match ? match[1].toLowerCase() : null;
116
+ })
117
+ .filter(Boolean);
118
+ const changeTypes = [...new Set([...fromEnv, ...fromCommits])];
119
+
120
+ const labels = (process.env.VERIFY_LABELS || "")
121
+ .split(",")
122
+ .map(value => value.trim())
123
+ .filter(Boolean);
124
+
125
+ return { changedFiles, changeTypes, labels };
126
+ }
127
+
128
+ /**
129
+ * CLI entry: evaluate and exit non-zero when a required verification delta is missing.
130
+ * @returns {void}
131
+ */
132
+ function main() {
133
+ const context = gatherContext();
134
+ const result = evaluateVerificationCoverage(context);
135
+ console.log(
136
+ `[verification-coverage] types=[${context.changeTypes.join(
137
+ ","
138
+ )}] labels=[${context.labels.join(",")}]`
139
+ );
140
+ console.log(`[verification-coverage] ${result.reason}`);
141
+ if (result.exempt) {
142
+ console.log(
143
+ "[verification-coverage] EXEMPT (logged): proceeding without a verification delta for a declared non-behavioral change."
144
+ );
145
+ }
146
+ if (!result.ok) {
147
+ console.error(`[verification-coverage] FAIL: ${result.reason}`);
148
+ process.exit(1);
149
+ }
150
+ console.log("[verification-coverage] OK");
151
+ }
152
+
153
+ // Run only when invoked directly — importing for tests must have no side effects.
154
+ if (
155
+ process.argv[1] &&
156
+ import.meta.url === pathToFileURL(process.argv[1]).href
157
+ ) {
158
+ main();
159
+ }